| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.IO.Abstractions;
- using System.Linq;
- using System.Net.Http;
- using System.Net.Http.Headers;
- using System.Text;
- using System.Threading.Tasks;
- using BO.AppServer.Metadata.Dto;
- using Quadarax.Foundation.Core.Data.Interface;
- using Quadarax.Foundation.Core.Data.Interface.Entity;
- using Quadarax.Foundation.Core.Data.Interface.Entity.Dto;
- using Quadarax.Foundation.Core.Json;
- using Quadarax.Foundation.Core.Logging;
- using Quadarax.Foundation.Core.Object;
- using Quadarax.Foundation.Core.Value.Extensions;
- namespace BO.Connector
- {
- public abstract class AbstractConnection : DisposableObject
- {
- #region *** Private fields ***
- private HttpClient _client;
- private Uri _uriApiBase;
- private ILogHandler _log;
- private TimeSpan _timeout;
- private string _ticket;
- private Binder _binder;
- private bool _isDumpContentEnabled;
- protected abstract string ApiName { get; }
- protected bool IsOpen => !string.IsNullOrEmpty(_ticket);
- protected string Ticket => _ticket;
- #endregion
- #region *** Constructors ***
- protected AbstractConnection(Uri uriApiBase, TimeSpan timeout, ILogHandler log = null, bool isDumpContentEnabled = false)
- {
- _uriApiBase = uriApiBase ?? throw new ArgumentNullException(nameof(uriApiBase));
- _uriApiBase = new Uri(_uriApiBase.AbsoluteUri + ApiName + "/");
- _log = log;
- _timeout = timeout;
- _binder = new Binder(new FileSystem());
- _binder.SerializerOptions.IgnoreReadOnlyProperties = true;
- _binder.SerializerOptions.IgnoreNullValues = true;
- _isDumpContentEnabled = isDumpContentEnabled;
- }
- #endregion
- #region *** Public Operations ***
- public void Open(string userName, string userPassword, string magicKey)
- {
- Log(LogSeverityEnum.Trace, $"Opening Connector '{ApiName}' open with magicKey='{magicKey}'");
- _client = CreateClient();
- InitTicket(magicKey);
- Login(userName, userPassword);
- OnOpening();
- Log(LogSeverityEnum.Debug, $"Connector '{ApiName}' open for user '{userName}' with timeout setting {_timeout}.");
- }
- public void Close()
- {
- _client?.CancelPendingRequests();
- _client?.Dispose();
- _client = null;
- _ticket = string.Empty;
- Log(LogSeverityEnum.Debug, $"Connector '{ApiName}' closed.");
- }
- public bool Ping(out TimeSpan duration, out string status)
- {
- CheckIsOpen();
- var start = DateTime.Now;
- var ping = Task.Run(() =>CallRequestAsync<ResultValueDto<PingDto>>($"{_ticket}/ping", RestCallTypeEnum.Get, null, null, true));
- ping.Wait();
- duration = DateTime.Now - start;
- if (ping.Result == null)
- {
- status = string.Empty;
- return false;
- }
- status = ping.Result.Value.Status;
- return ping.Result.IsSuccess;
- }
- #endregion
- #region *** Virtuals & Overrides ***
- protected override void OnDisposing()
- {
- _client?.Dispose();
- }
- #endregion
- #region *** Private Operations ***
- protected void CheckIsOpen()
- {
- if (!IsOpen)
- throw new InvalidOperationException(
- "Connector is not open to call specific operation. Call Open() first.");
- }
- protected void Log(LogSeverityEnum type, string message, Exception e = null)
- {
- _log?.Log(type, message, e);
- }
- protected void InitTicket(string magicKey)
- {
- var result = Task.Run(() => CallRequestAsync<ResultValueDto<AuthDto>>($"{magicKey}/auth", RestCallTypeEnum.Get));;
- result.Wait();
- if (!result.Result.IsSuccess)
- throw result.Result.ToAggregateException();
- _ticket = result.Result.Value.Ticket;
- }
- protected void Login(string userName, string userPassword)
- {
- var headerAtts = new Dictionary<string, string>();
- headerAtts.Add("userName",userName);
- headerAtts.Add("userPassword",userPassword);
- var result = Task.Run(() => CallRequestAsync<ResultValueDto<UserRDto>>($"{_ticket}/login", RestCallTypeEnum.Post, new ResultPlain(), headerAtts));
- result.Wait();
- if (!result.Result.IsSuccess)
- throw result.Result.ToAggregateException();
- Log(LogSeverityEnum.Info,$"User '{result.Result.Value.Name}' is logged in.");
- }
- protected IDictionary<string, string> GetPagingHeaderAttributes(PagingDto paging, IDictionary<string,string> source = null)
- {
- if (paging == null || paging.IsDisabled)
- return null;//paging = new PagingDto();
- if (source == null)
- source = new Dictionary<string, string>();
- source.Add(new KeyValuePair<string, string>("page", paging.Page.ToString()));
- source.Add(new KeyValuePair<string, string>("pageSize", paging.PageSize.ToString()));
- return source;
- }
- protected IDictionary<string, string> GetApiPasswordHeaderAttributes(string apiKeyPassword, IDictionary<string,string> source = null)
- {
- if (source == null)
- source = new Dictionary<string, string>();
- source.Add(new KeyValuePair<string, string>("apiKeyPassword", apiKeyPassword));
- return source;
- }
- protected IDictionary<string, string> GetUserContextHeaderAttributes(string userNameContext, IDictionary<string,string> source = null)
- {
- if (source == null)
- source = new Dictionary<string, string>();
- source.Add(new KeyValuePair<string, string>("context", userNameContext));
- return source;
- }
- protected async Task<Stream> CallGetStreamAsync(string relativeUrlApiCall,
- IDictionary<string, string> headerAttributes = null, bool silent = false)
- {
- Log(LogSeverityEnum.Trace, $"Calling REST API GET STREAM {_uriApiBase.AbsoluteUri + relativeUrlApiCall}");
- if (headerAttributes != null)
- {
- foreach (var key in headerAttributes.Keys)
- {
- _client.DefaultRequestHeaders.Remove(key);
- _client.DefaultRequestHeaders.Add(key,headerAttributes[key]);
- }
- }
-
- return await _client.GetStreamAsync(relativeUrlApiCall);
- }
- protected async Task<TResult> CallPostStreamMultipartAsync<TResult>(string relativeUrlApiCall,
- string fileName,Stream stream,string streamMimeType, IDictionary<string, string> headerAttributes = null, bool silent = false)
- where TResult : ResultDto
- {
- Log(LogSeverityEnum.Trace, $"Calling REST API POST-MULTI-STREAM {_uriApiBase.AbsoluteUri + relativeUrlApiCall}");
- var boundary = Guid.NewGuid().ToString();
- var mpc = new MultipartFormDataContent(boundary);
- mpc.Headers.Remove("Content-Type");
- mpc.Headers.TryAddWithoutValidation("Content-Type", "multipart/form-data; boundary=" + boundary);
- var stc = new StreamContent(stream);
- stc.Headers.ContentType = new MediaTypeHeaderValue(streamMimeType);
- mpc.Add(stc, "content", fileName);
- using (var rq = new HttpRequestMessage())
- {
- rq.Content = mpc;
- rq.Method = new System.Net.Http.HttpMethod("POST");
- rq.RequestUri = new Uri(_uriApiBase.AbsoluteUri + relativeUrlApiCall);
- rq.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/octet-stream"));
- if (headerAttributes != null)
- foreach (var key in headerAttributes.Keys)
- rq.Headers.Add(key, headerAttributes[key]);
- var start = DateTime.Now;
- var resp = await _client.SendAsync(rq);
- //var resp = await cli.PostAsync(relativeUrlApiCall, rqdMulti);
- Log(LogSeverityEnum.Trace,
- $"REST API POST-MULTI-STREAM {_uriApiBase.AbsoluteUri + relativeUrlApiCall} done @ {(DateTime.Now - start).ToReadableString()}");
- if (!resp.IsSuccessStatusCode)
- {
- var message =
- $"Calling REST API POST-MULTI-STREAM {_uriApiBase.AbsoluteUri + relativeUrlApiCall} ends with result code '{resp.StatusCode}'";
- Log(LogSeverityEnum.Warn, message);
- throw new Exception(message);
- }
- var content = await resp.Content.ReadAsStringAsync();
- if (_isDumpContentEnabled)
- Log(LogSeverityEnum.Trace, content);
- var result = (TResult)_binder.LoadFromString(content, typeof(TResult));
- if (result.IsSuccess) return result;
- if (silent) return result;
- // handle errors
- var excps = new List<Exception>();
- foreach (var err in result.Errors)
- {
- var exc = new Exception($"[{err.Code}] {err.Message}");
- exc.Data.Add("Code", err.Code);
- excps.Add(exc);
- }
- throw new AggregateException(excps);
- }
- }
- protected async Task<TResult> CallRequestAsync<TResult>(string relativeUrlApiCall,RestCallTypeEnum method, IDto data = null,IDictionary<string,string> headerAttributes = null, bool silent = false)
- where TResult : ResultDto
- {
- if (data == null)
- if(method != RestCallTypeEnum.Get && method != RestCallTypeEnum.GetStream && method != RestCallTypeEnum.Delete)
- throw new ArgumentNullException(nameof(data), $"For REST API method '{method}' data must be specified!");
- Log(LogSeverityEnum.Trace, $"Calling REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall}");
- var start = DateTime.Now;
- HttpResponseMessage resp = null;
- SetDefaultClientHeaders(_client);
- HttpContentHeaders reqHeaders = null;
- switch (method)
- {
- case RestCallTypeEnum.Get:
- if (headerAttributes != null)
- {
- foreach (var key in headerAttributes.Keys)
- {
- _client.DefaultRequestHeaders.Remove(key);
- _client.DefaultRequestHeaders.Add(key,headerAttributes[key]);
- }
- }
- resp = await _client.GetAsync(relativeUrlApiCall);
- break;
- case RestCallTypeEnum.Post:
- var req = new StringContent(_binder.SaveToString(data));
- reqHeaders = req.Headers;
- if (headerAttributes!=null)
- foreach (var key in headerAttributes.Keys)
- {
- //if (req.Headers.Contains(key))
- req.Headers.Remove(key);
- req.Headers.Add(key, headerAttributes[key]);
- }
- req.Headers.ContentType = new MediaTypeHeaderValue("application/json");
- resp = await _client.PostAsync(relativeUrlApiCall, req);
- break;
- case RestCallTypeEnum.Put:
- req = new StringContent(_binder.SaveToString(data));
- reqHeaders = req.Headers;
- if (headerAttributes!=null)
- foreach (var key in headerAttributes.Keys)
- {
- // if (req.Headers.Contains(key))
- req.Headers.Remove(key);
- req.Headers.Add(key, headerAttributes[key]);
- }
- req.Headers.ContentType = new MediaTypeHeaderValue("application/json");
- resp = await _client.PutAsync(relativeUrlApiCall, req);
- break;
- case RestCallTypeEnum.Delete:
- resp = await _client.DeleteAsync(relativeUrlApiCall);
- break;
- default:
- throw new NotSupportedException($"{method} REST API operation is not implemented.");
- }
-
- // may remove paging headers after call
- /*
- if (headerAttributes != null)
- foreach (var key in headerAttributes.Keys)
- _client.DefaultRequestHeaders.Remove(key);
- */
- Log(LogSeverityEnum.Trace, $"REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall} done @ {(DateTime.Now - start).ToReadableString()}");
- if (!resp.IsSuccessStatusCode)
- {
- var message = $"Calling REST API {method} {_uriApiBase.AbsoluteUri + relativeUrlApiCall} ends with result code '{resp.StatusCode}'";
- Log(LogSeverityEnum.Warn, message);
- throw new Exception(message);
- }
- var content = await resp.Content.ReadAsStringAsync();
- if (_isDumpContentEnabled)
- {
- Log(LogSeverityEnum.Trace, DumpHttpClient(method,relativeUrlApiCall, _client, reqHeaders));
- Log(LogSeverityEnum.Trace, content);
- }
- var result = (TResult)_binder.LoadFromString(content, typeof(TResult));
- if (result.IsSuccess) return result;
- // No data exception pass result
- if (!result.IsSuccess && result.Errors.Any(x => x.Code == 1099)) return result;
- if (silent) return result;
- // handle errors
- var excps = new List<Exception>();
- foreach (var err in result.Errors)
- {
- var exc = new Exception($"[{err.Code}] {err.Message}");
- exc.Data.Add("Code", err.Code);
- excps.Add(exc);
- }
- throw new AggregateException(excps);
- }
- protected virtual void OnOpening()
- {
- }
- private HttpClient CreateClient()
- {
- var client = new HttpClient();
- client.BaseAddress = new Uri(_uriApiBase, ApiName);
- client.Timeout = _timeout;
- SetDefaultClientHeaders(client);
- return client;
- }
- private void SetDefaultClientHeaders(HttpClient client)
- {
- client.DefaultRequestHeaders.Clear();
- client.DefaultRequestHeaders.Add("User-Agent", "BO.Connector." + ApiName);
- // Add an Accept header for JSON format.
- client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
- client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("multipart/mixed"));
- //client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream"));
- }
- private string DumpHttpClient(RestCallTypeEnum method, string relativeUrl, HttpClient client, HttpContentHeaders headers)
- {
- var sb = new StringBuilder();
- sb.Append($"RQ {method.ToString().ToUpper()} to url '{_client.BaseAddress + relativeUrl}':").AppendLine();
- sb.Append($"rq version: {_client.DefaultRequestVersion}").AppendLine();
- sb.Append($"rq timeout: {_client.Timeout.ToReadableString()}").AppendLine();
- sb.Append("Headers:").AppendLine();
- foreach (var header in _client.DefaultRequestHeaders)
- sb.Append(header.Key).Append(" : ").Append(string.Join(",", header.Value)).AppendLine();
- sb.Append(DumpHttpHeaders(headers));
- return sb.ToString();
- }
- private string DumpHttpHeaders(HttpContentHeaders headers)
- {
- var sb = new StringBuilder();
- sb.Append("Headers:").AppendLine();
- if (headers != null)
- {
- foreach (var header in headers)
- sb.Append(header.Key).Append(" : ").Append(string.Join(",", header.Value)).AppendLine();
- }
- return sb.ToString();
- }
- #endregion
- }
- }
|