AbstractConnection.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO.Abstractions;
  4. using System.Net.Http;
  5. using System.Net.Http.Headers;
  6. using System.Text.Json;
  7. using System.Threading.Tasks;
  8. using BO.AppServer.Metadata.Dto;
  9. using Quadarax.Foundation.Core.Data.Interface;
  10. using Quadarax.Foundation.Core.Data.Interface.Entity;
  11. using Quadarax.Foundation.Core.Data.Interface.Entity.Dto;
  12. using Quadarax.Foundation.Core.Json;
  13. using Quadarax.Foundation.Core.Logging;
  14. using Quadarax.Foundation.Core.Object;
  15. using Quadarax.Foundation.Core.Value.Extensions;
  16. namespace BO.Connector
  17. {
  18. public abstract class AbstractConnection : DisposableObject
  19. {
  20. #region *** Private fields ***
  21. private HttpClient _client;
  22. private Uri _uriApiBase;
  23. private ILogHandler _log;
  24. private TimeSpan _timeout;
  25. private string _ticket;
  26. private Binder _binder;
  27. private bool _isDumpContentEnabled;
  28. protected abstract string ApiName { get; }
  29. protected bool IsOpen => !string.IsNullOrEmpty(_ticket);
  30. protected string Ticket => _ticket;
  31. #endregion
  32. #region *** Constructors ***
  33. protected AbstractConnection(Uri uriApiBase, TimeSpan timeout, ILogHandler log = null, bool isDumpContentEnabled = false)
  34. {
  35. _uriApiBase = uriApiBase ?? throw new ArgumentNullException(nameof(uriApiBase));
  36. _uriApiBase = new Uri(_uriApiBase.AbsoluteUri + ApiName + "/");
  37. _log = log;
  38. _timeout = timeout;
  39. _binder = new Binder(new FileSystem());
  40. _binder.SerializerOptions.IgnoreReadOnlyProperties = true;
  41. _binder.SerializerOptions.IgnoreNullValues = true;
  42. _isDumpContentEnabled = isDumpContentEnabled;
  43. }
  44. #endregion
  45. #region *** Public Operations ***
  46. public void Open(string userName, string userPassword, string magicKey)
  47. {
  48. Log(LogSeverityEnum.Trace, $"Opening Connector '{ApiName}' open with magicKey='{magicKey}'");
  49. _client = CreateClient();
  50. InitTicket(magicKey);
  51. Login(userName, userPassword);
  52. Log(LogSeverityEnum.Debug, $"Connector '{ApiName}' open for user '{userName}' with timeout setting {_timeout}.");
  53. }
  54. public void Close()
  55. {
  56. _client?.CancelPendingRequests();
  57. _client?.Dispose();
  58. _client = null;
  59. _ticket = string.Empty;
  60. Log(LogSeverityEnum.Debug, $"Connector '{ApiName}' closed.");
  61. }
  62. public bool Ping(out TimeSpan duration, out string status)
  63. {
  64. CheckIsOpen();
  65. var start = DateTime.Now;
  66. var ping = Task.Run(() =>CallRequestAsync<ResultValueDto<PingDto>>($"{_ticket}/ping", RestCallTypeEnum.Get, null, null, true));
  67. ping.Wait();
  68. duration = DateTime.Now - start;
  69. if (ping.Result == null)
  70. {
  71. status = string.Empty;
  72. return false;
  73. }
  74. status = ping.Result.Value.Status;
  75. return ping.Result.IsSuccess;
  76. }
  77. #endregion
  78. #region *** Virtuals & Overrides ***
  79. protected override void OnDisposing()
  80. {
  81. _client?.Dispose();
  82. }
  83. #endregion
  84. #region *** Private Operations ***
  85. protected void CheckIsOpen()
  86. {
  87. if (!IsOpen)
  88. throw new InvalidOperationException(
  89. "Connector is not open to call specific operation. Call Open() first.");
  90. }
  91. protected void Log(LogSeverityEnum type, string message, Exception e = null)
  92. {
  93. _log?.Log(type, message, e);
  94. }
  95. protected void InitTicket(string magicKey)
  96. {
  97. var result = Task.Run(() => CallRequestAsync<ResultValueDto<AuthDto>>($"{magicKey}/auth", RestCallTypeEnum.Get));;
  98. result.Wait();
  99. if (!result.Result.IsSuccess)
  100. throw result.Result.ToAggregateException();
  101. _ticket = result.Result.Value.Ticket;
  102. }
  103. protected void Login(string userName, string userPassword)
  104. {
  105. var headerAtts = new Dictionary<string, string>();
  106. headerAtts.Add("userName",userName);
  107. headerAtts.Add("userPassword",userPassword);
  108. var result = Task.Run(() => CallRequestAsync<ResultValueDto<UserRDto>>($"{_ticket}/login", RestCallTypeEnum.Post, new ResultPlain(), headerAtts));
  109. result.Wait();
  110. if (!result.Result.IsSuccess)
  111. throw result.Result.ToAggregateException();
  112. Log(LogSeverityEnum.Info,$"User '{result.Result.Value.Name}' is logged in.");
  113. }
  114. protected IDictionary<string, string> GetPagingHeaderAttributes(PagingDto paging)
  115. {
  116. if (paging == null || paging.IsDisabled)
  117. return null;//paging = new PagingDto();
  118. var result = new Dictionary<string, string>
  119. {
  120. { "page", paging.Page.ToString() },
  121. { "pageSize", paging.PageSize.ToString() }
  122. };
  123. return result;
  124. }
  125. protected async Task<TResult> CallRequestAsync<TResult>(string relativeUrlApiCall,RestCallTypeEnum method, IDto data = null,IDictionary<string,string> headerAttributes = null, bool silent = false) where TResult : ResultDto
  126. {
  127. if (data == null)
  128. if(method != RestCallTypeEnum.Get && method != RestCallTypeEnum.GetStream && method != RestCallTypeEnum.Delete)
  129. throw new ArgumentNullException(nameof(data), $"For REST API method '{method}' data must be specified!");
  130. Log(LogSeverityEnum.Trace, $"Calling REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall}");
  131. var start = DateTime.Now;
  132. HttpResponseMessage resp = null;
  133. switch (method)
  134. {
  135. case RestCallTypeEnum.Get:
  136. if (headerAttributes != null)
  137. {
  138. foreach (var key in headerAttributes.Keys)
  139. {
  140. _client.DefaultRequestHeaders.Remove(key);
  141. _client.DefaultRequestHeaders.Add(key,headerAttributes[key]);
  142. }
  143. }
  144. resp = await _client.GetAsync(relativeUrlApiCall);
  145. break;
  146. case RestCallTypeEnum.Post:
  147. var req = new StringContent(_binder.SaveToString(data));
  148. if (headerAttributes!=null)
  149. foreach (var key in headerAttributes.Keys)
  150. req.Headers.Add(key,headerAttributes[key]);
  151. req.Headers.ContentType = new MediaTypeHeaderValue("application/json");
  152. resp = await _client.PostAsync(relativeUrlApiCall, req);
  153. break;
  154. case RestCallTypeEnum.Put:
  155. req = new StringContent(_binder.SaveToString(data));
  156. if (headerAttributes!=null)
  157. foreach (var key in headerAttributes.Keys)
  158. req.Headers.Add(key,headerAttributes[key]);
  159. req.Headers.ContentType = new MediaTypeHeaderValue("application/json");
  160. resp = await _client.PutAsync(relativeUrlApiCall, req);
  161. break;
  162. case RestCallTypeEnum.Delete:
  163. resp = await _client.DeleteAsync(relativeUrlApiCall);
  164. break;
  165. default:
  166. throw new NotSupportedException($"{method} REST API operation is not implemented.");
  167. }
  168. // may remove paging headers after call
  169. /*
  170. if (headerAttributes != null)
  171. foreach (var key in headerAttributes.Keys)
  172. _client.DefaultRequestHeaders.Remove(key);
  173. */
  174. Log(LogSeverityEnum.Trace, $"REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall} done @ {(DateTime.Now - start).ToReadableString()}");
  175. if (!resp.IsSuccessStatusCode)
  176. {
  177. var message = $"Calling REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall} ends with result code '{resp.StatusCode}'";
  178. Log(LogSeverityEnum.Warn, message);
  179. throw new Exception(message);
  180. }
  181. var content = await resp.Content.ReadAsStringAsync();
  182. if (_isDumpContentEnabled)
  183. Log(LogSeverityEnum.Trace, content);
  184. var result = (TResult)_binder.LoadFromString(content, typeof(TResult));
  185. if (result.IsSuccess) return result;
  186. if (silent) return result;
  187. // handle errors
  188. var excps = new List<Exception>();
  189. foreach (var err in result.Errors)
  190. {
  191. var exc = new Exception($"[{err.Code}] {err.Message}");
  192. exc.Data.Add("Code", err.Code);
  193. excps.Add(exc);
  194. }
  195. throw new AggregateException(excps);
  196. }
  197. private HttpClient CreateClient()
  198. {
  199. var client = new HttpClient();
  200. client.BaseAddress = new Uri(_uriApiBase, ApiName);
  201. client.Timeout = _timeout;
  202. client.DefaultRequestHeaders.Accept.Clear();
  203. client.DefaultRequestHeaders.Add("User-Agent", "BO.Connector." + ApiName);
  204. // Add an Accept header for JSON format.
  205. client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
  206. return client;
  207. }
  208. #endregion
  209. }
  210. }