Exemple #1
0
        /// <summary>
        /// Updates an existing attachment entry with the provided files. If the file already exists
        /// in the attachment entry, it will be replaced. Otherwise, a new attachment line is added.
        /// </summary>
        /// <param name="attachmentEntry">
        /// The attachment entry ID to be updated.
        /// </param>
        /// <param name="files">
        /// A Dictionary containing the files to be updated, where the file name is the Key and the file is the Value.
        /// </param>
        public async Task PatchAttachmentsAsync(int attachmentEntry, IDictionary <string, Stream> files)
        {
            await ExecuteRequest(async() =>
            {
                if (files == null || files.Count == 0)
                {
                    throw new ArgumentException("No files to be sent.");
                }

                var result = await ServiceLayerRoot
                             .AppendPathSegment($"Attachments2({attachmentEntry})")
                             .WithCookies(Cookies)
                             .PatchMultipartAsync(mp =>
                {
                    // Removes double quotes from boundary, otherwise the request fails with error 405 Method Not Allowed
                    var boundary   = mp.Headers.ContentType.Parameters.First(o => o.Name.Equals("boundary", StringComparison.OrdinalIgnoreCase));
                    boundary.Value = boundary.Value.Replace("\"", string.Empty);

                    foreach (var file in files)
                    {
                        var content = new StreamContent(file.Value);
                        content.Headers.Add("Content-Disposition", $"form-data; name=\"files\"; filename=\"{file.Key}\"");
                        content.Headers.Add("Content-Type", "application/octet-stream");
                        mp.Add(content);
                    }
                });

                return(result);
            });
        }
Exemple #2
0
        /// <summary>
        /// Performs the ping request with the provided path segment.
        /// </summary>
        private async Task <SLPingResponse> ExecutePingAsync(string path)
        {
            try
            {
                var response = await ServiceLayerRoot
                               .RemovePath()
                               .AppendPathSegment(path)
                               .GetAsync();

                var pingResponse = await response.GetJsonAsync <SLPingResponse>();

                pingResponse.IsSuccessStatusCode = response.ResponseMessage.IsSuccessStatusCode;
                pingResponse.StatusCode          = response.ResponseMessage.StatusCode;
                return(pingResponse);
            }
            catch (FlurlHttpException ex)
            {
                try
                {
                    if (ex.Call.HttpResponseMessage == null)
                    {
                        throw;
                    }
                    var pingResponse = await ex.GetResponseJsonAsync <SLPingResponse>();

                    pingResponse.IsSuccessStatusCode = ex.Call.HttpResponseMessage.IsSuccessStatusCode;
                    pingResponse.StatusCode          = ex.Call.HttpResponseMessage.StatusCode;
                    return(pingResponse);
                }
                catch { throw ex; }
            }
            catch (Exception) { throw; }
        }
Exemple #3
0
        /// <summary>
        /// Performs a POST Logout request, ending the current session.
        /// </summary>
        public async Task LogoutAsync()
        {
            if (Cookies == null)
            {
                return;
            }

            try
            {
                await ServiceLayerRoot.AppendPathSegment("Logout").WithCookies(Cookies).PostAsync();

                _loginResponse = new SLLoginResponse();
                _lastRequest   = default;
                Cookies        = null;
            }
            catch (FlurlHttpException ex)
            {
                try
                {
                    if (ex.Call.HttpResponseMessage == null)
                    {
                        throw;
                    }
                    var response = await ex.GetResponseJsonAsync <SLResponseError>();

                    throw new SLException(response.Error.Message.Value, response.Error, ex);
                }
                catch { throw ex; }
            }
        }
Exemple #4
0
        /// <summary>
        /// Downloads the specified attachment file as a <see cref="byte"/> array. By default, the first attachment
        /// line is downloaded if there are multiple attachment lines in one attachment.
        /// </summary>
        /// <param name="attachmentEntry">
        /// The attachment entry ID to be downloaded.
        /// </param>
        /// <param name="fileName">
        /// The file name of the attachment to be downloaded  (including the file extension). Only required if
        /// you want to download an attachment line other than the first attachment line.
        /// </param>
        /// <returns>
        /// The downloaded attachment file as a <see cref="byte"/> array.
        /// </returns>
        public async Task <byte[]> GetAttachmentAsBytesAsync(int attachmentEntry, string fileName = null)
        {
            return(await ExecuteRequest(async() =>
            {
                var file = await ServiceLayerRoot
                           .AppendPathSegment($"Attachments2({attachmentEntry})/$value")
                           .SetQueryParam("filename", !string.IsNullOrEmpty(fileName) ? $"'{fileName}'" : null)
                           .WithCookies(Cookies)
                           .GetBytesAsync();

                return file;
            }));
        }
Exemple #5
0
        /// <summary>
        /// Performs the POST Login request to the Service Layer.
        /// </summary>
        /// <param name="forceLogin">
        /// Whether the login request should be forced even if the current session has not expired.
        /// </param>
        /// <param name="expectReturn">
        /// Wheter the login information should be returned.
        /// </param>
        private async Task <SLLoginResponse> ExecuteLoginAsync(bool forceLogin = false, bool expectReturn = false)
        {
            // Prevents multiple login requests in a multi-threaded scenario
            await _semaphoreSlim.WaitAsync();

            try
            {
                if (forceLogin)
                {
                    _lastRequest = default;
                }

                // Session still valid, no need to login again
                if (DateTime.Now.Subtract(_lastRequest).TotalMinutes < _loginResponse.SessionTimeout)
                {
                    return(expectReturn ? LoginResponse : null);
                }

                var loginResponse = await ServiceLayerRoot
                                    .AppendPathSegment("Login")
                                    .WithCookies(out var cookieJar)
                                    .PostJsonAsync(new { CompanyDB, UserName, Password, Language })
                                    .ReceiveJson <SLLoginResponse>();

                Cookies                  = cookieJar;
                _loginResponse           = loginResponse;
                _loginResponse.LastLogin = DateTime.Now;

                return(expectReturn ? LoginResponse : null);
            }
            catch (FlurlHttpException ex)
            {
                try
                {
                    if (ex.Call.HttpResponseMessage == null)
                    {
                        throw;
                    }
                    var response = await ex.GetResponseJsonAsync <SLResponseError>();

                    throw new SLException(response.Error.Message.Value, response.Error, ex);
                }
                catch { throw ex; }
            }
            finally
            {
                _semaphoreSlim.Release();
            }
        }
Exemple #6
0
        /// <summary>
        /// Builds the multipart content for a batch request using multiple change sets.
        /// </summary>
        private MultipartContent BuildMixedMultipartContent(SLBatchRequest batchRequest, string boundary)
        {
            var multipartContent = new MultipartContent("mixed", boundary);
            var request          = new HttpRequestMessage(batchRequest.HttpMethod, Url.Combine(ServiceLayerRoot.ToString(), batchRequest.Resource));

            if (batchRequest.Data != null)
            {
                request.Content = new StringContent(JsonConvert.SerializeObject(batchRequest.Data, batchRequest.JsonSerializerSettings), batchRequest.Encoding, "application/json");
            }

            if (batchRequest.ContentID.HasValue)
            {
                request.Content.Headers.Add("Content-ID", batchRequest.ContentID.ToString());
            }

            var innerContent = new HttpMessageContent(request);

            innerContent.Headers.Add("content-transfer-encoding", "binary");
            multipartContent.Add(innerContent);

            return(multipartContent);
        }
Exemple #7
0
        /// <summary>
        /// Performs a batch request (multiple operations sent in a single HTTP request) with the provided <see cref="SLBatchRequest"/> collection.
        /// </summary>
        /// <remarks>
        /// See section 'Batch Operations' in the Service Layer User Manual for more details.
        /// </remarks>
        /// <param name="requests">
        /// A collection of <see cref="SLBatchRequest"/> to be sent in the batch.</param>
        /// <param name="singleChangeSet">
        /// Whether all the requests in this batch should be sent in a single change set. This means that any unsuccessful request will cause the whole batch to be rolled back.
        /// </param>
        /// <returns>
        /// An <see cref="HttpResponseMessage"/> array containg the response messages of the batch request.
        /// </returns>
        public async Task <HttpResponseMessage[]> PostBatchAsync(IEnumerable <SLBatchRequest> requests, bool singleChangeSet = true)
        {
            // Adds required "msgtype" parameter to the HttpContent in order for the ReadAsHttpResponseMessageAsync method to work as expected
            void AddMsgTypeToHttpContent(HttpContent httpContent)
            {
                if (httpContent.Headers.ContentType.MediaType.Equals("application/http", StringComparison.OrdinalIgnoreCase) &&
                    !httpContent.Headers.ContentType.Parameters.Any(p => p.Name.Equals("msgtype", StringComparison.OrdinalIgnoreCase) &&
                                                                    p.Value.Equals("response", StringComparison.OrdinalIgnoreCase)))
                {
                    httpContent.Headers.ContentType.Parameters.Add(new NameValueHeaderValue("msgtype", "response"));
                }
            }

            return(await ExecuteRequest(async() =>
            {
                if (requests == null || requests.Count() == 0)
                {
                    throw new ArgumentException("No requests to be sent.");
                }

                var response = singleChangeSet
                    ? await ServiceLayerRoot
                               .AppendPathSegment("$batch")
                               .WithCookies(Cookies)
                               .PostMultipartAsync(mp =>
                {
                    mp.Headers.ContentType.MediaType = "multipart/mixed";
                    mp.Add(BuildMixedMultipartContent(requests));
                })
                    : await ServiceLayerRoot
                               .AppendPathSegment("$batch")
                               .WithCookies(Cookies)
                               .PostMultipartAsync(mp =>
                {
                    mp.Headers.ContentType.MediaType = "multipart/mixed";
                    foreach (var request in requests)
                    {
                        string boundary = "changeset_" + Guid.NewGuid();
                        mp.Add(BuildMixedMultipartContent(request, boundary));
                    }
                });

                var responseList = new List <HttpResponseMessage>();
                var multipart = await response.ResponseMessage.Content.ReadAsMultipartAsync();

                foreach (HttpContent httpContent in multipart.Contents)
                {
                    if (httpContent.Headers.ContentType.MediaType.Equals("application/http", StringComparison.OrdinalIgnoreCase))
                    {
                        AddMsgTypeToHttpContent(httpContent);
                        var innerResponse = await httpContent.ReadAsHttpResponseMessageAsync();
                        responseList.Add(innerResponse);
                    }
                    else if (httpContent.Headers.ContentType.MediaType.Equals("multipart/mixed", StringComparison.OrdinalIgnoreCase))
                    {
                        var innerMultipart = await httpContent.ReadAsMultipartAsync();

                        foreach (HttpContent innerHttpContent in innerMultipart.Contents)
                        {
                            AddMsgTypeToHttpContent(innerHttpContent);
                            var innerResponse = await innerHttpContent.ReadAsHttpResponseMessageAsync();
                            responseList.Add(innerResponse);
                        }
                    }
                }

                return responseList.ToArray();
            }));
        }
Exemple #8
0
 /// <summary>
 /// Initializes a new instance of the <see cref="SLRequest"/> class that represents a request to the associated <see cref="SLConnection"/>.
 /// </summary>
 /// <remarks>
 /// The request can be configured using the extension methods provided in <see cref="SLRequestExtensions"/>.
 /// </remarks>
 /// <param name="resource">
 /// The resource name to be requested.
 /// </param>
 /// <param name="id">
 /// The entity ID to be requested.
 /// </param>
 public SLRequest Request(string resource, object id)
 {
     return(new SLRequest(this, new FlurlRequest(ServiceLayerRoot.AppendPathSegment(id is string?$"{resource}('{id}')" : $"{resource}({id})"))));;
 }
Exemple #9
0
 /// <summary>
 /// Initializes a new instance of the <see cref="SLRequest"/> class that represents a request to the associated <see cref="SLConnection"/>.
 /// </summary>
 /// <remarks>
 /// The request can be configured using the extension methods provided in <see cref="SLRequestExtensions"/>.
 /// </remarks>
 /// <param name="resource">
 /// The resource name to be requested.
 /// </param>
 public SLRequest Request(string resource)
 {
     return(new SLRequest(this, new FlurlRequest(ServiceLayerRoot.AppendPathSegment(resource))));
 }