Add project
Basic formatting applied. Unnecessary comments have been removed. Suspicious code is covered by TODO.
This commit is contained in:
@ -0,0 +1,34 @@
|
||||
using System.ServiceModel.Channels;
|
||||
using System.ServiceModel.Description;
|
||||
using System.ServiceModel.Dispatcher;
|
||||
|
||||
namespace Hcs.ClientApi.RemoteCaller
|
||||
{
|
||||
public class GostSigningEndpointBehavior : IEndpointBehavior
|
||||
{
|
||||
private HcsClientConfig clientConfig;
|
||||
|
||||
public GostSigningEndpointBehavior(HcsClientConfig clientConfig)
|
||||
{
|
||||
this.clientConfig = clientConfig;
|
||||
}
|
||||
|
||||
public void Validate(ServiceEndpoint endpoint)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
|
||||
{
|
||||
}
|
||||
|
||||
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
|
||||
{
|
||||
}
|
||||
|
||||
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
|
||||
{
|
||||
clientRuntime.MessageInspectors.Add(
|
||||
new GostSigningMessageInspector(clientConfig));
|
||||
}
|
||||
}
|
||||
}
|
||||
120
Hcs.Client/ClientApi/RemoteCaller/GostSigningMessageInspector.cs
Normal file
120
Hcs.Client/ClientApi/RemoteCaller/GostSigningMessageInspector.cs
Normal file
@ -0,0 +1,120 @@
|
||||
using Hcs.GostXades;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.ServiceModel;
|
||||
using System.ServiceModel.Channels;
|
||||
using System.ServiceModel.Dispatcher;
|
||||
using System.Text;
|
||||
using System.Xml;
|
||||
|
||||
namespace Hcs.ClientApi.RemoteCaller
|
||||
{
|
||||
/// <summary>
|
||||
/// Фильтр сообщений добавляет в XML сообщение электронную подпись XADES/GOST.
|
||||
/// </summary>
|
||||
internal class GostSigningMessageInspector : IClientMessageInspector
|
||||
{
|
||||
private HcsClientConfig clientConfig;
|
||||
|
||||
public GostSigningMessageInspector(HcsClientConfig clientConfig)
|
||||
{
|
||||
this.clientConfig = clientConfig;
|
||||
}
|
||||
|
||||
public object BeforeSendRequest(ref Message request, IClientChannel channel)
|
||||
{
|
||||
try
|
||||
{
|
||||
string filterHeader = " Фильтр отправки:";
|
||||
|
||||
PurgeDebuggerHeaders(ref request);
|
||||
var messageBody = GetMessageBodyString(ref request, Encoding.UTF8);
|
||||
|
||||
if (!messageBody.Contains(HcsConstants.SignedXmlElementId))
|
||||
{
|
||||
clientConfig.MaybeCaptureMessage(true, messageBody);
|
||||
}
|
||||
else
|
||||
{
|
||||
string certInfo = HcsX509Tools.ДатьСтрокуФИОСертификатаСДатойОкончания(clientConfig.Certificate);
|
||||
clientConfig.Log($"{filterHeader} подписываю сообщение ключем [{certInfo}]...");
|
||||
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
var service = new GostXadesBesService(clientConfig.CryptoProviderType);
|
||||
var signedXml = service.Sign(messageBody,
|
||||
HcsConstants.SignedXmlElementId,
|
||||
clientConfig.CertificateThumbprint,
|
||||
clientConfig.CertificatePassword);
|
||||
stopwatch.Stop();
|
||||
|
||||
clientConfig.Log($"{filterHeader} сообщение подписано за {stopwatch.ElapsedMilliseconds}мс.");
|
||||
|
||||
clientConfig.MaybeCaptureMessage(true, signedXml);
|
||||
|
||||
request = Message.CreateMessage(
|
||||
XmlReaderFromString(signedXml), int.MaxValue, request.Version);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
string error = $"В {GetType().Name} произошло исключение";
|
||||
throw new Exception(error, ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void AfterReceiveReply(ref Message reply, object correlationState)
|
||||
{
|
||||
clientConfig.MaybeCaptureMessage(false, reply.ToString());
|
||||
}
|
||||
|
||||
private void PurgeDebuggerHeaders(ref Message request)
|
||||
{
|
||||
int limit = request.Headers.Count;
|
||||
for (int i = 0; i < limit; ++i)
|
||||
{
|
||||
if (request.Headers[i].Name.Equals("VsDebuggerCausalityData"))
|
||||
{
|
||||
request.Headers.RemoveAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string GetMessageBodyString(ref Message request, Encoding encoding)
|
||||
{
|
||||
MessageBuffer mb = request.CreateBufferedCopy(int.MaxValue);
|
||||
|
||||
request = mb.CreateMessage();
|
||||
|
||||
Stream s = new MemoryStream();
|
||||
XmlWriter xw = XmlWriter.Create(s);
|
||||
mb.CreateMessage().WriteMessage(xw);
|
||||
xw.Flush();
|
||||
s.Position = 0;
|
||||
|
||||
byte[] bXML = new byte[s.Length];
|
||||
s.Read(bXML, 0, (int)s.Length);
|
||||
|
||||
if (bXML[0] != (byte)'<')
|
||||
{
|
||||
return encoding.GetString(bXML, 3, bXML.Length - 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
return encoding.GetString(bXML, 0, bXML.Length);
|
||||
}
|
||||
}
|
||||
|
||||
XmlReader XmlReaderFromString(String xml)
|
||||
{
|
||||
var stream = new MemoryStream();
|
||||
var writer = new System.IO.StreamWriter(stream);
|
||||
writer.Write(xml);
|
||||
writer.Flush();
|
||||
stream.Position = 0;
|
||||
return XmlReader.Create(stream);
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Hcs.Client/ClientApi/RemoteCaller/HcsPagedResultState.cs
Normal file
66
Hcs.Client/ClientApi/RemoteCaller/HcsPagedResultState.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
|
||||
namespace Hcs.ClientApi.RemoteCaller
|
||||
{
|
||||
/// <summary>
|
||||
/// Состояние многостраничной выдачи для методов HCS выдыющих длинные списки.
|
||||
/// Списки выдаются порциями по 100 позиций и в каждой порции указано состояние
|
||||
/// многостраничной выдачи одним значением - это либо bool со значением true что
|
||||
/// означает что эта порция последняя IsLastPage, либо это строка содержащая
|
||||
/// guid объекта начала следующей порции - и этот guid надо указать в запросе
|
||||
/// чтобы получить следующую порцию.
|
||||
/// </summary>
|
||||
public class HcsPagedResultState
|
||||
{
|
||||
/// <summary>
|
||||
/// Состояние указыввает что это последняя страница
|
||||
/// </summary>
|
||||
public bool IsLastPage { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Состояние указывает что это не последняя страница и
|
||||
/// следующая страница начинается с NextGuid
|
||||
/// </summary>
|
||||
public Guid NextGuid { get; private set; }
|
||||
|
||||
private const string me = nameof(HcsPagedResultState);
|
||||
|
||||
public static readonly HcsPagedResultState IsLastPageResultState = new HcsPagedResultState(true);
|
||||
|
||||
/// <summary>
|
||||
/// Новый маркер состояния многостраничной выдачи метода HCS
|
||||
/// </summary>
|
||||
public HcsPagedResultState(object item)
|
||||
{
|
||||
if (item == null) throw new HcsException($"{me}.Item is null");
|
||||
|
||||
if (item is bool)
|
||||
{
|
||||
if ((bool)item == false) throw new HcsException($"{me}.IsLastPage is false");
|
||||
IsLastPage = true;
|
||||
}
|
||||
else if (item is string)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLastPage = false;
|
||||
NextGuid = HcsUtil.ParseGuid((string)item);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new HcsException($"Failed to parse {me}.NextGuid value", e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new HcsException($"{me}.Item is of unrecognized type " + item.GetType().FullName);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{me}({nameof(IsLastPage)}={IsLastPage}" +
|
||||
(IsLastPage ? "" : $",{nameof(NextGuid)}={NextGuid}") + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
373
Hcs.Client/ClientApi/RemoteCaller/HcsRemoteCallMethod.cs
Normal file
373
Hcs.Client/ClientApi/RemoteCaller/HcsRemoteCallMethod.cs
Normal file
@ -0,0 +1,373 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using System.ServiceModel;
|
||||
using System.ServiceModel.Channels;
|
||||
using System.ServiceModel.Description;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Hcs.ClientApi.RemoteCaller
|
||||
{
|
||||
/// <summary>
|
||||
/// Базовый класс для методов HCS вызываемых удаленно
|
||||
/// </summary>
|
||||
public abstract class HcsRemoteCallMethod
|
||||
{
|
||||
public HcsClientConfig _config;
|
||||
protected CustomBinding _binding;
|
||||
|
||||
/// <summary>
|
||||
/// Для методов возвращающих мало данных можно попробовать сократить
|
||||
/// начальный период ожидания подготовки ответа
|
||||
/// </summary>
|
||||
public bool EnableMinimalResponseWaitDelay { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Для противодействия зависанию ожидания вводится предел ожидания в минутах
|
||||
/// для методов которые можно перезапустить заново с теми же параметрами.
|
||||
/// С периодом в 120 минут 09.2024 не успевали за ночь получить все данные.
|
||||
/// </summary>
|
||||
public int RestartTimeoutMinutes = 20;
|
||||
|
||||
/// <summary>
|
||||
/// Можно ли этот метод перезапускать в случае зависания ожидания или в случае сбоя на сервере?
|
||||
/// </summary>
|
||||
public bool CanBeRestarted { get; protected set; }
|
||||
|
||||
public HcsClientConfig ClientConfig => _config;
|
||||
|
||||
public HcsRemoteCallMethod(HcsClientConfig config)
|
||||
{
|
||||
this._config = config;
|
||||
ConfigureBinding();
|
||||
}
|
||||
|
||||
private void ConfigureBinding()
|
||||
{
|
||||
_binding = new CustomBinding();
|
||||
|
||||
// Эксперимент 19.07.2022 возникает ошибка WCF (TimeoutException 60 сек)
|
||||
_binding.ReceiveTimeout = TimeSpan.FromSeconds(180);
|
||||
_binding.OpenTimeout = TimeSpan.FromSeconds(180);
|
||||
_binding.SendTimeout = TimeSpan.FromSeconds(180);
|
||||
_binding.CloseTimeout = TimeSpan.FromSeconds(180);
|
||||
|
||||
_binding.Elements.Add(new TextMessageEncodingBindingElement
|
||||
{
|
||||
MessageVersion = MessageVersion.Soap11,
|
||||
WriteEncoding = Encoding.UTF8
|
||||
});
|
||||
|
||||
if (_config.UseTunnel)
|
||||
{
|
||||
if (System.Diagnostics.Process.GetProcessesByName("stunnel").Any() ? false : true)
|
||||
{
|
||||
throw new Exception("stunnel не запущен");
|
||||
}
|
||||
|
||||
_binding.Elements.Add(new HttpTransportBindingElement
|
||||
{
|
||||
AuthenticationScheme = (_config.IsPPAK ? System.Net.AuthenticationSchemes.Digest : System.Net.AuthenticationSchemes.Basic),
|
||||
MaxReceivedMessageSize = int.MaxValue,
|
||||
UseDefaultWebProxy = false
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_binding.Elements.Add(new HttpsTransportBindingElement
|
||||
{
|
||||
AuthenticationScheme = (_config.IsPPAK ? System.Net.AuthenticationSchemes.Digest : System.Net.AuthenticationSchemes.Basic),
|
||||
MaxReceivedMessageSize = int.MaxValue,
|
||||
UseDefaultWebProxy = false,
|
||||
RequireClientCertificate = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected EndpointAddress GetEndpointAddress(string endpointName)
|
||||
{
|
||||
return new EndpointAddress(_config.ComposeEndpointUri(endpointName));
|
||||
}
|
||||
|
||||
protected void ConfigureEndpointCredentials(
|
||||
ServiceEndpoint serviceEndpoint, ClientCredentials clientCredentials)
|
||||
{
|
||||
serviceEndpoint.EndpointBehaviors.Add(new GostSigningEndpointBehavior(_config));
|
||||
|
||||
if (!_config.IsPPAK)
|
||||
{
|
||||
clientCredentials.UserName.UserName = HcsConstants.UserAuth.Name;
|
||||
clientCredentials.UserName.Password = HcsConstants.UserAuth.Passwd;
|
||||
|
||||
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate (
|
||||
object sender, X509Certificate serverCertificate, X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
return true;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
bool letSystemValidateServerCertificate = false;
|
||||
if (!letSystemValidateServerCertificate)
|
||||
{
|
||||
System.Net.ServicePointManager.ServerCertificateValidationCallback = delegate (
|
||||
object sender, X509Certificate serverCertificate, X509Chain chain, System.Net.Security.SslPolicyErrors sslPolicyErrors)
|
||||
{
|
||||
// 06.06.2024 возникла ошибка "Это может быть связано с тем, что сертификат сервера
|
||||
// не настроен должным образом с помощью HTTP.SYS в случае HTTPS."
|
||||
// ГИС ЖКХ заменил сертификат сервера HTTPS и System.Net не смогла проверить новый.
|
||||
// В похожем случае необходимо включить "return true" чтобы любой сертификат
|
||||
// без проверки принимался (или найти файл lk_api_dom_gosuslugi_ru.cer нового сертификата
|
||||
// сервера ГИС ЖКХ API в разделе "Регламенты и инструкции" портала dom.gosuslugi.ru
|
||||
// и установить этот сертификат текущему пользователю).
|
||||
// Файл сертификата сервера API в разделе "Регламенты и инструкции" называется, например, так:
|
||||
// "Сертификат открытого ключа для организации защищенного TLS соединения с сервисами
|
||||
// легковесной интеграции (c 10.06.2024)".
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!_config.UseTunnel)
|
||||
{
|
||||
clientCredentials.ClientCertificate.SetCertificate(
|
||||
StoreLocation.CurrentUser,
|
||||
StoreName.My,
|
||||
X509FindType.FindByThumbprint,
|
||||
_config.CertificateThumbprint);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполнение одной попытки пооучить результат операции.
|
||||
/// Реализуется в производных классах.
|
||||
/// </summary>
|
||||
protected abstract Task<IHcsGetStateResult> TryGetResultAsync(IHcsAck sourceAck, CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Основной алгоритм ожидания ответа на асинхронный запрос.
|
||||
/// Из документации ГИС ЖКХ:
|
||||
/// Также рекомендуем придерживаться следующего алгоритма отправки запросов на получение статуса обработки пакета в случае использования асинхронных сервисов ГИС ЖКХ (в рамках одного MessageGUID):
|
||||
/// - первый запрос getState направлять не ранее чем через 10 секунд, после получения квитанции о приеме пакета с бизнес-данными от сервиса ГИС КЖХ;
|
||||
/// - в случае, если на первый запрос getSate получен результат с RequestState равным "1" или "2", то следующий запрос getState необходимо направлять не ранее чем через 60 секунд после отправки предыдущего запроса;
|
||||
/// - в случае, если на второй запрос getSate получен результат с RequestState равным "1" или "2", то следующий запрос getState необходимо направлять не ранее чем через 300 секунд после отправки предыдущего запроса;
|
||||
/// - в случае, если на третий запрос getSate получен результат с RequestState равным "1" или "2", то следующий запрос getState необходимо направлять не ранее чем через 900 секунд после отправки предыдущего запроса;
|
||||
/// - в случае, если на четвертый(и все последующие запросы) getState получен результат с RequestState равным "1" или "2", то следующий запрос getState необходимо направлять не ранее чем через 1800 секунд после отправки предыдущего запроса.
|
||||
/// </summary>
|
||||
protected async Task<IHcsGetStateResult> WaitForResultAsync(
|
||||
IHcsAck ack, bool withInitialDelay, CancellationToken token)
|
||||
{
|
||||
var startTime = DateTime.Now;
|
||||
IHcsGetStateResult result;
|
||||
for (int attempts = 1; ; attempts++)
|
||||
{
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
int delaySec = EnableMinimalResponseWaitDelay ? 2 : 5;
|
||||
if (attempts >= 2) delaySec = 5;
|
||||
if (attempts >= 3) delaySec = 10;
|
||||
if (attempts >= 5) delaySec = 20;
|
||||
if (attempts >= 7) delaySec = 40;
|
||||
if (attempts >= 9) delaySec = 80;
|
||||
if (attempts >= 12) delaySec = 300;
|
||||
|
||||
if (attempts > 1 || withInitialDelay)
|
||||
{
|
||||
var minutesElapsed = (int)(DateTime.Now - startTime).TotalMinutes;
|
||||
if (CanBeRestarted && minutesElapsed > RestartTimeoutMinutes)
|
||||
throw new HcsRestartTimeoutException($"Превышено ожидание в {RestartTimeoutMinutes} минут");
|
||||
|
||||
_config.Log($"Ожидаю {delaySec} сек. до попытки #{attempts}" +
|
||||
$" получить ответ (ожидание {minutesElapsed} минут(ы))...");
|
||||
|
||||
await Task.Delay(delaySec * 1000, token);
|
||||
}
|
||||
|
||||
_config.Log($"Запрашиваю ответ, попытка #{attempts} {ThreadIdText}...");
|
||||
result = await TryGetResultAsync(ack, token);
|
||||
if (result != null) break;
|
||||
}
|
||||
|
||||
_config.Log($"Ответ получен, число частей: {result.Items.Count()}");
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Исполнение повторяемой операции некоторое дпустимое число ошибок
|
||||
/// </summary>
|
||||
public async Task<T> RunRepeatableTaskAsync<T>(
|
||||
Func<Task<T>> taskFunc, Func<Exception, bool> canIgnoreFunc, int maxAttempts)
|
||||
{
|
||||
for (int attempts = 1; ; attempts++)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await taskFunc();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (canIgnoreFunc(e))
|
||||
{
|
||||
if (attempts < maxAttempts)
|
||||
{
|
||||
Log($"Игнорирую {attempts} из {maxAttempts} допустимых ошибок");
|
||||
continue;
|
||||
}
|
||||
throw new HcsException(
|
||||
$"Более {maxAttempts} продолжений после допустимых ошибок", e);
|
||||
}
|
||||
throw new HcsException("Вложенная ошибка", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Для запросов к серверу которые можно направлять несколько раз, разрешаем
|
||||
/// серверу аномально отказаться. Предполагается, что здесь мы игнорируем
|
||||
/// только жесткие отказы серверной инфраструктуры, которые указывают
|
||||
/// что запрос даже не был принят в обработку. Также все запросы на
|
||||
/// чтение можно повторять в случае их серверных системных ошибок.
|
||||
/// </summary>
|
||||
protected async Task<T> RunRepeatableTaskInsistentlyAsync<T>(
|
||||
Func<Task<T>> func, CancellationToken token)
|
||||
{
|
||||
int afterErrorDelaySec = 120;
|
||||
for (int attempt = 1; ; attempt++)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await func();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
string marker;
|
||||
if (CanIgnoreSuchException(e, out marker))
|
||||
{
|
||||
_config.Log($"Игнорирую ошибку #{attempt} типа [{marker}].");
|
||||
_config.Log($"Ожидаю {afterErrorDelaySec} сек. до повторения после ошибки...");
|
||||
await Task.Delay(afterErrorDelaySec * 1000, token);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e is HcsRestartTimeoutException)
|
||||
throw new HcsRestartTimeoutException("Наступило событие рестарта", e);
|
||||
|
||||
// Ошибки удаленной системы, которые нельзя игнорировать, дублируем для точности перехвата
|
||||
if (e is HcsRemoteException) throw HcsRemoteException.CreateNew(e as HcsRemoteException);
|
||||
throw new HcsException("Ошибка, которую нельзя игнорировать", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "[EXP001000] Произошла ошибка при передаче данных. Попробуйте осуществить передачу данных повторно",
|
||||
// Видимо, эту ошибку нельзя включать здесь. Предположительно это маркер DDOS защиты и если отправлять
|
||||
// точно такой же пакет повторно, то ошибка входит в бесконечный цикл - необходимо заново
|
||||
// собирать пакет с новыми кодами и временем и новой подписью. Такую ошибку надо обнаруживать
|
||||
// на более высоком уровне и заново отправлять запрос новым пакетом. (21.09.2022)
|
||||
private static string[] ignorableSystemErrorMarkers = {
|
||||
"Истекло время ожидания шлюза",
|
||||
"Базовое соединение закрыто: Соединение, которое должно было работать, было разорвано сервером",
|
||||
"Попробуйте осуществить передачу данных повторно", // Включено 18.10.2024, HouseManagement API сильно сбоит
|
||||
"(502) Недопустимый шлюз",
|
||||
"(503) Сервер не доступен"
|
||||
};
|
||||
|
||||
private bool CanIgnoreSuchException(Exception e, out string resultMarker)
|
||||
{
|
||||
foreach (var marker in ignorableSystemErrorMarkers)
|
||||
{
|
||||
var found = HcsUtil.EnumerateInnerExceptions(e).Find(
|
||||
x => x.Message != null && x.Message.Contains(marker));
|
||||
if (found != null)
|
||||
{
|
||||
resultMarker = marker;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
resultMarker = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет массив @items на содержание строго одного элемента типа @T и этот элемент
|
||||
/// </summary>
|
||||
protected T RequireSingleItem<T>(object[] items)
|
||||
{
|
||||
if (items == null)
|
||||
throw new HcsException($"Array of type {typeof(T)} must not be null");
|
||||
if (items.Length == 0)
|
||||
throw new HcsException($"Array of type {typeof(T)} must not be empty");
|
||||
if (items.Length > 1)
|
||||
throw new HcsException($"Array of type {typeof(T)} must contain 1 element, not {items.Length} of type {items[0].GetType().FullName}");
|
||||
return RequireType<T>(items[0]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Проверяет @obj на соответствие типу @T и возвращает преобразованный объект
|
||||
/// </summary>
|
||||
protected T RequireType<T>(object obj)
|
||||
{
|
||||
if (obj != null)
|
||||
{
|
||||
if (typeof(T) == obj.GetType()) return (T)obj;
|
||||
}
|
||||
|
||||
throw new HcsException(
|
||||
$"Require object of type {typeof(T)} but got" +
|
||||
(obj == null ? "null" : obj.GetType().FullName));
|
||||
}
|
||||
|
||||
internal static HcsException NewUnexpectedObjectException(object obj)
|
||||
{
|
||||
if (obj == null) return new HcsException("unexpected object is null");
|
||||
return new HcsException($"Unexpected object [{obj}] of type {obj.GetType().FullName}");
|
||||
}
|
||||
|
||||
public static string FormatGuid(Guid guid) => HcsUtil.FormatGuid(guid);
|
||||
|
||||
public static string FormatGuid(Guid? guid) => (guid != null) ? FormatGuid((Guid)guid) : null;
|
||||
|
||||
public static Guid ParseGuid(string guid) => HcsUtil.ParseGuid(guid);
|
||||
|
||||
public static Guid ParseGuid(object obj)
|
||||
{
|
||||
if (obj == null) throw new HcsException("Can't parse null as Guid");
|
||||
if (obj is Guid) return (Guid)obj;
|
||||
return ParseGuid(obj.ToString());
|
||||
}
|
||||
|
||||
public static Guid[] ParseGuidArray(string[] array)
|
||||
{
|
||||
if (array == null) return null;
|
||||
return array.ToList().Select(x => ParseGuid(x)).ToArray();
|
||||
}
|
||||
|
||||
public bool IsArrayEmpty(Array a) => (a == null || a.Length == 0);
|
||||
|
||||
public string MakeEmptyNull(string s)
|
||||
{
|
||||
return string.IsNullOrEmpty(s) ? null : s;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Выполняет @action на объекте @x если объект не пустой и приводится к типу @T
|
||||
/// </summary>
|
||||
public void CallOnType<T>(object x, Action<T> action) where T : class
|
||||
{
|
||||
var t = x as T;
|
||||
if (t != null) action(t);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает индентификатор текущего исполняемого потока
|
||||
/// </summary>
|
||||
public int ThreadId => System.Environment.CurrentManagedThreadId;
|
||||
|
||||
public string ThreadIdText => $"(thread #{ThreadId})";
|
||||
|
||||
public void Log(string message) => ClientConfig.Log(message);
|
||||
}
|
||||
}
|
||||
84
Hcs.Client/ClientApi/RemoteCaller/HcsRequestHelper.cs
Normal file
84
Hcs.Client/ClientApi/RemoteCaller/HcsRequestHelper.cs
Normal file
@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Hcs.ClientApi.RemoteCaller
|
||||
{
|
||||
public static class HcsRequestHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Подготовка заголовка сообщения отправляемого в ГИС ЖКХ с обязательными атрибутами.
|
||||
/// Заголовки могут быть разного типа для разных типов сообщений но имена полей одинаковые.
|
||||
/// </summary>
|
||||
public static THeaderType CreateHeader<THeaderType>(HcsClientConfig config) where THeaderType : class
|
||||
{
|
||||
try
|
||||
{
|
||||
var instance = Activator.CreateInstance(typeof(THeaderType));
|
||||
|
||||
foreach (var prop in instance.GetType().GetProperties())
|
||||
{
|
||||
switch (prop.Name)
|
||||
{
|
||||
case "Item":
|
||||
prop.SetValue(instance, config.OrgPPAGUID);
|
||||
break;
|
||||
case "ItemElementName":
|
||||
prop.SetValue(instance, Enum.Parse(prop.PropertyType, "orgPPAGUID"));
|
||||
break;
|
||||
case "MessageGUID":
|
||||
prop.SetValue(instance, Guid.NewGuid().ToString());
|
||||
break;
|
||||
case "Date":
|
||||
prop.SetValue(instance, DateTime.Now);
|
||||
break;
|
||||
case "IsOperatorSignatureSpecified":
|
||||
if (config.Role == HcsOrganizationRoles.RC || config.Role == HcsOrganizationRoles.RSO)
|
||||
prop.SetValue(instance, true);
|
||||
break;
|
||||
case "IsOperatorSignature":
|
||||
if (config.Role == HcsOrganizationRoles.RC || config.Role == HcsOrganizationRoles.RSO)
|
||||
prop.SetValue(instance, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return instance as THeaderType;
|
||||
}
|
||||
catch (ArgumentNullException ex)
|
||||
{
|
||||
throw new ApplicationException($"При сборке заголовка запроса для ГИС произошла ошибка: {ex.Message}");
|
||||
}
|
||||
catch (SystemException exc)
|
||||
{
|
||||
throw new ApplicationException($"При сборке заголовка запроса для ГИС произошла не предвиденная ошибка {exc.GetBaseException().Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Для объекта запроса возвращает значение строки свойства version
|
||||
/// </summary>
|
||||
public static string GetRequestVersionString(object requestObject)
|
||||
{
|
||||
if (requestObject == null) return null;
|
||||
object versionHost = requestObject;
|
||||
|
||||
if (versionHost != null)
|
||||
{
|
||||
var versionProperty = versionHost.GetType().GetProperties().FirstOrDefault(x => x.Name == "version");
|
||||
if (versionProperty != null) return versionProperty.GetValue(versionHost) as string;
|
||||
}
|
||||
|
||||
foreach (var field in requestObject.GetType().GetFields())
|
||||
{
|
||||
versionHost = field.GetValue(requestObject);
|
||||
if (versionHost != null)
|
||||
{
|
||||
var versionProperty = versionHost.GetType().GetProperties().FirstOrDefault(x => x.Name == "version");
|
||||
if (versionProperty != null) return versionProperty.GetValue(versionHost) as string;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Hcs.Client/ClientApi/RemoteCaller/HcsServicePointConfig.cs
Normal file
18
Hcs.Client/ClientApi/RemoteCaller/HcsServicePointConfig.cs
Normal file
@ -0,0 +1,18 @@
|
||||
namespace Hcs.ClientApi.RemoteCaller
|
||||
{
|
||||
/// <summary>
|
||||
/// Конфигурация ServicePointManager для работы с TLS. Скорее всего класс не нужен.
|
||||
/// </summary>
|
||||
public static class HcsServicePointConfig
|
||||
{
|
||||
public static void InitConfig()
|
||||
{
|
||||
// TODO: Проверить комментарий
|
||||
// Отключено 15.12.2023, работает и так
|
||||
//ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
|
||||
//ServicePointManager.CheckCertificateRevocationList = false;
|
||||
//ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);
|
||||
//ServicePointManager.Expect100Continue = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
8
Hcs.Client/ClientApi/RemoteCaller/IHcsAck.cs
Normal file
8
Hcs.Client/ClientApi/RemoteCaller/IHcsAck.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Hcs.ClientApi.RemoteCaller
|
||||
{
|
||||
public interface IHcsAck
|
||||
{
|
||||
string MessageGUID { get; set; }
|
||||
string RequesterMessageGUID { get; set; }
|
||||
}
|
||||
}
|
||||
8
Hcs.Client/ClientApi/RemoteCaller/IHcsFault.cs
Normal file
8
Hcs.Client/ClientApi/RemoteCaller/IHcsFault.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Hcs.ClientApi.RemoteCaller
|
||||
{
|
||||
public interface IHcsFault
|
||||
{
|
||||
string ErrorCode { get; set; }
|
||||
string ErrorMessage { get; set; }
|
||||
}
|
||||
}
|
||||
7
Hcs.Client/ClientApi/RemoteCaller/IHcsGetStateResult.cs
Normal file
7
Hcs.Client/ClientApi/RemoteCaller/IHcsGetStateResult.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Hcs.ClientApi.RemoteCaller
|
||||
{
|
||||
public interface IHcsGetStateResult
|
||||
{
|
||||
object[] Items { get; set; }
|
||||
}
|
||||
}
|
||||
10
Hcs.Client/ClientApi/RemoteCaller/IHcsHeaderType.cs
Normal file
10
Hcs.Client/ClientApi/RemoteCaller/IHcsHeaderType.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Hcs.ClientApi.RemoteCaller
|
||||
{
|
||||
public interface IHcsHeaderType
|
||||
{
|
||||
string MessageGUID { get; set; }
|
||||
DateTime Date { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user