diff --git a/.gitignore b/.gitignore index 55423de..0250353 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# General macOS +.DS_Store +.AppleDouble +.LSOverride + # Build results bin/ obj/ diff --git a/Bynder/Sample/ApiSample.cs b/Bynder/Sample/ApiSample.cs index 9b74a90..2e692f2 100644 --- a/Bynder/Sample/ApiSample.cs +++ b/Bynder/Sample/ApiSample.cs @@ -2,10 +2,7 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System; -using System.Configuration; -using Bynder.Sdk; using Bynder.Sdk.Service; -using Bynder.Sample; using Bynder.Sample.Utils; using Bynder.Sdk.Query.Asset; using Bynder.Sdk.Query.Collection; @@ -24,6 +21,13 @@ public class ApiSample /// /// arguments to main public static void Main(string[] args) + { + //Choose your authentication method by commenting one of these lines. + PermanentToken(); + Oauth(); + } + + private static void PermanentToken() { using (var client = ClientFactory.Create(Configuration.FromJson("Config.json"))) { @@ -41,34 +45,33 @@ public static void Main(string[] args) Console.WriteLine(collection.Name); } } + } + private static void Oauth() + { using (var waitForToken = new WaitForToken()) + using (var listener = new UrlHttpListener("http://localhost:8888/", waitForToken)) + using (var client = ClientFactory.Create(Configuration.FromJson("Config.json"))) { - using (var listener = new UrlHttpListener("http://localhost:8888/", waitForToken)) - { - using (var client = ClientFactory.Create(Configuration.FromJson("Config.json"))) - { - Browser.Launch(client.GetOAuthService().GetAuthorisationUrl("state example", "offline asset:read collection:read")); - waitForToken.WaitHandle.WaitOne(50000); + Browser.Launch(client.GetOAuthService().GetAuthorisationUrl("state example", "offline asset:read collection:read")); + waitForToken.WaitHandle.WaitOne(50000); - if (waitForToken.Success) - { - client.GetOAuthService().GetAccessTokenAsync(waitForToken.Token, "offline asset:read collection:read").Wait(); + if (waitForToken.Success) + { + client.GetOAuthService().GetAccessTokenAsync(waitForToken.Token, "offline asset:read collection:read").Wait(); - var mediaList = client.GetAssetService().GetMediaListAsync(new MediaQuery()).Result; + var mediaList = client.GetAssetService().GetMediaListAsync(new MediaQuery()).Result; - foreach (var media in mediaList) - { - Console.WriteLine(media.Name); - } + foreach (var media in mediaList) + { + Console.WriteLine(media.Name); + } - var collectionList = client.GetCollectionService().GetCollectionsAsync(new GetCollectionsQuery()).Result; + var collectionList = client.GetCollectionService().GetCollectionsAsync(new GetCollectionsQuery()).Result; - foreach (var collection in collectionList) - { - Console.WriteLine(collection.Name); - } - } + foreach (var collection in collectionList) + { + Console.WriteLine(collection.Name); } } } diff --git a/Bynder/Sdk/Api/RequestSender/ApiRequestSender.cs b/Bynder/Sdk/Api/RequestSender/ApiRequestSender.cs index 45a1d9f..e32903e 100644 --- a/Bynder/Sdk/Api/RequestSender/ApiRequestSender.cs +++ b/Bynder/Sdk/Api/RequestSender/ApiRequestSender.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Bynder.Sdk.Model; using Bynder.Sdk.Api.Requests; -using Bynder.Sdk.Service; using Newtonsoft.Json; using Bynder.Sdk.Query.Decoder; using Bynder.Sdk.Query; @@ -28,6 +27,8 @@ internal class ApiRequestSender : IApiRequestSender private SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private IHttpRequestSender _httpSender; + public const string TokenPath = "/v6/authentication/oauth2/token"; + /// /// Initializes a new instance of the class. /// @@ -90,13 +91,21 @@ public async Task SendRequestAsync(Requests.Request request) } var httpRequest = CreateHttpRequest(request); - var responseString = await _httpSender.SendHttpRequest(httpRequest).ConfigureAwait(false); - if (!string.IsNullOrEmpty(responseString)) + var response = await _httpSender.SendHttpRequest(httpRequest).ConfigureAwait(false); + + var responseContent = response.Content; + if (response.Content == null) + { + return default(T); + } + + var responseString = await responseContent.ReadAsStringAsync().ConfigureAwait(false); + if (string.IsNullOrEmpty(responseString)) { - return JsonConvert.DeserializeObject(responseString); + return default(T); } - return default(T); + return JsonConvert.DeserializeObject(responseString); } private HttpRequestMessage CreateHttpRequest(Requests.Request request) @@ -131,7 +140,7 @@ private async Task RefreshToken() { Authenticated = false, Query = query, - Path = $"/v6/authentication/oauth2/token", + Path = TokenPath, HTTPMethod = HttpMethod.Post }; diff --git a/Bynder/Sdk/Api/RequestSender/HttpRequestSender.cs b/Bynder/Sdk/Api/RequestSender/HttpRequestSender.cs index efafd6c..3de9ecf 100644 --- a/Bynder/Sdk/Api/RequestSender/HttpRequestSender.cs +++ b/Bynder/Sdk/Api/RequestSender/HttpRequestSender.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Net.Http; +using System.Reflection; using System.Threading.Tasks; namespace Bynder.Sdk.Api.RequestSender @@ -14,17 +15,25 @@ internal class HttpRequestSender : IHttpRequestSender { private readonly HttpClient _httpClient = new HttpClient(); + /// + /// User-Agent header we add to each request. + /// + public string UserAgent + { + get { return $"bynder-c-sharp-sdk/{Assembly.GetExecutingAssembly().GetName().Version.ToString()}"; } + } + /// - /// Sends the HTTP request and returns the content as string. + /// Sends the HTTP request and returns its response. /// /// The HTTP request response. /// HTTP request. - public async Task SendHttpRequest(HttpRequestMessage httpRequest) + public async Task SendHttpRequest(HttpRequestMessage httpRequest) { + httpRequest.Headers.Add("User-Agent", UserAgent); var response = await _httpClient.SendAsync(httpRequest).ConfigureAwait(false); - response.EnsureSuccessStatusCode(); - return await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return response; } /// diff --git a/Bynder/Sdk/Api/RequestSender/IHttpRequestSender.cs b/Bynder/Sdk/Api/RequestSender/IHttpRequestSender.cs index 7b52963..d46f2b4 100644 --- a/Bynder/Sdk/Api/RequestSender/IHttpRequestSender.cs +++ b/Bynder/Sdk/Api/RequestSender/IHttpRequestSender.cs @@ -14,10 +14,10 @@ namespace Bynder.Sdk.Api.RequestSender internal interface IHttpRequestSender : IDisposable { /// - /// Sends the HTTP request and returns the content as string. + /// Sends the HTTP request and returns its response. /// /// The HTTP request response. /// HTTP request. - Task SendHttpRequest(HttpRequestMessage httpRequest); + Task SendHttpRequest(HttpRequestMessage httpRequest); } } diff --git a/Bynder/Sdk/Bynder.Sdk.csproj b/Bynder/Sdk/Bynder.Sdk.csproj index c4688c3..ac17e6e 100644 --- a/Bynder/Sdk/Bynder.Sdk.csproj +++ b/Bynder/Sdk/Bynder.Sdk.csproj @@ -6,10 +6,29 @@ Bynder Bynder.Sdk Copyright © Bynder + true + 2.2.0 + BynderDevops + The main goal of this SDK is to speed up the integration of Bynder customers who use C# making it easier to connect to the Bynder API (http://docs.bynder.apiary.io/) and executing requests on it. + https://bynder.com/static/3.0/img/favicon-black.ico + en + https://github.com/Bynder/bynder-c-sharp-sdk/blob/master/LICENSE + true + BynderDevops + https://github.com/Bynder/bynder-c-sharp-sdk + Adds the SDK's name and version to each request in the User-Agent header, to enable better insight in the Bynder API usage. + The main goal of this SDK is to speed up the integration of Bynder customers who use C# making it easier to connect to the Bynder API (http://docs.bynder.apiary.io/) and executing requests on it. + Bynder API C# SDK + Bynder.Sdk + Bynder.Sdk + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + diff --git a/Bynder/Test/Api/RequestSender/ApiRequestSenderTest.cs b/Bynder/Test/Api/RequestSender/ApiRequestSenderTest.cs index 4a8bf95..8d98bdc 100644 --- a/Bynder/Test/Api/RequestSender/ApiRequestSenderTest.cs +++ b/Bynder/Test/Api/RequestSender/ApiRequestSenderTest.cs @@ -15,147 +15,127 @@ namespace Bynder.Test.Api.RequestSender { public class ApiRequestSenderTest { - [Fact] - public async Task WhenRequestIsPostThenParametersAreAddedToContent() + private const string _accessToken = "some_access_token"; + private const string _authHeader = "Bearer " + _accessToken; + private const string _path = "/fake/api"; + private const string _queryValue = "some_query_value"; + private const string _queryString = "Item1=" + _queryValue; + + private Mock _httpSenderMock; + private StubQuery _query; + + public ApiRequestSenderTest() { - var httpSenderMock = new Mock(); - var query = new StubQuery + _httpSenderMock = new Mock(); + _httpSenderMock + .Setup(sender => sender.SendHttpRequest(It.IsAny())) + .Returns(Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK))); + _query = new StubQuery { - Item1 = "Value" + Item1 = _queryValue }; - var accessToken = "access_token"; + } - using (ApiRequestSender apiRequestSender = new ApiRequestSender( - new Configuration{ - BaseUrl = new Uri("https://example.bynder.com"), - }, - GetCredentials(true, accessToken), - httpSenderMock.Object - )) - { - var apiRequest = new ApiRequest() - { - Path = "/fake/api", - HTTPMethod = HttpMethod.Post, - Query = query - }; - - await apiRequestSender.SendRequestAsync(apiRequest); - - httpSenderMock.Verify(sender => sender.SendHttpRequest( - It.Is( - req => req.RequestUri.PathAndQuery.Contains("/fake/api") - && req.Method == HttpMethod.Post - && req.Headers.Authorization.ToString() == $"Bearer {accessToken}" - && req.Content.ReadAsStringAsync().Result.Contains("Item1=Value") - ))); - - httpSenderMock.Verify(sender => sender.SendHttpRequest( - It.IsAny() - ), Times.Once); - } + [Fact] + public async Task WhenRequestIsPostThenParametersAreAddedToContent() + { + await SendRequest(hasValidCredentials: true, httpMethod: HttpMethod.Post); + + _httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.Is(req => + req.RequestUri.PathAndQuery.Contains(_path) + && req.Method == HttpMethod.Post + && req.Headers.Authorization.ToString() == _authHeader + && req.Content.ReadAsStringAsync().Result.Contains(_queryString) + ) + )); + + _httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.IsAny() + ), Times.Once); } [Fact] public async Task WhenCredentialInvalidTwoRequestsSent() { - var httpSenderMock = new Mock(); - - var query = new StubQuery - { - Item1 = "Value" - }; - var accessToken = "access_token"; - - using (ApiRequestSender apiRequestSender = new ApiRequestSender( - new Configuration{ - BaseUrl = new Uri("https://example.bynder.com"), - }, - GetCredentials(false, accessToken), - httpSenderMock.Object - )) - { - var apiRequest = new ApiRequest() - { - Path = "/fake/api", - HTTPMethod = HttpMethod.Get, - Query = query - }; - - await apiRequestSender.SendRequestAsync(apiRequest); - - httpSenderMock.Verify(sender => sender.SendHttpRequest( - It.Is( - req => req.RequestUri.PathAndQuery.Contains("/oauth2/token") - && req.Method == HttpMethod.Post - ))); - - httpSenderMock.Verify(sender => sender.SendHttpRequest( - It.Is( - req => req.RequestUri.PathAndQuery.Contains("/fake/api") - && req.Method == HttpMethod.Get - && req.Headers.Authorization.ToString() == $"Bearer {accessToken}" - && req.RequestUri.Query.Contains("Item1=Value") - ))); - - httpSenderMock.Verify(sender => sender.SendHttpRequest( - It.IsAny() - ), Times.Exactly(2)); - } + await SendRequest(hasValidCredentials: false, httpMethod: HttpMethod.Get); + + _httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.Is( + req => req.RequestUri.PathAndQuery == ApiRequestSender.TokenPath + && req.Method == HttpMethod.Post + ) + )); + + _httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.Is( + req => req.RequestUri.PathAndQuery.Contains(_path) + && req.Method == HttpMethod.Get + && req.Headers.Authorization.ToString() == _authHeader + && req.RequestUri.Query.Contains(_queryString) + ) + )); + + _httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.IsAny() + ), Times.Exactly(2)); } [Fact] public async Task WhenRequestIsGetThenParametersAreAddedToUrl() { - var httpSenderMock = new Mock(); - var query = new StubQuery - { - Item1 = "Value" - }; - var accessToken = "access_token"; + await SendRequest(hasValidCredentials: true, httpMethod: HttpMethod.Get); + + _httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.Is( + req => req.RequestUri.PathAndQuery.Contains(_path) + && req.Method == HttpMethod.Get + && req.Headers.Authorization.ToString() == _authHeader + && req.RequestUri.Query.Contains(_queryString) + ) + )); + + _httpSenderMock.Verify(sender => sender.SendHttpRequest( + It.IsAny() + ), Times.Once); + } + + private async Task SendRequest(bool hasValidCredentials, HttpMethod httpMethod) + { + await CreateApiRequestSender(hasValidCredentials).SendRequestAsync( + new ApiRequest() + { + Path = _path, + HTTPMethod = httpMethod, + Query = _query + } + ); + } - using (ApiRequestSender apiRequestSender = new ApiRequestSender( - new Configuration{ + private IApiRequestSender CreateApiRequestSender(bool hasValidCredentials) + { + return new ApiRequestSender( + new Configuration + { BaseUrl = new Uri("https://example.bynder.com"), }, - GetCredentials(true, accessToken), - httpSenderMock.Object - )) - { - var apiRequest = new ApiRequest - { - Path = "/fake/api", - HTTPMethod = HttpMethod.Get, - Query = query - }; - - await apiRequestSender.SendRequestAsync(apiRequest); - - httpSenderMock.Verify(sender => sender.SendHttpRequest( - It.Is( - req => req.RequestUri.PathAndQuery.Contains("/fake/api") - && req.Method == HttpMethod.Get - && req.Headers.Authorization.ToString() == $"Bearer {accessToken}" - && req.RequestUri.Query.Contains("Item1=Value") - ))); - - httpSenderMock.Verify(sender => sender.SendHttpRequest( - It.IsAny() - ), Times.Once); - } + GetCredentials(hasValidCredentials), + _httpSenderMock.Object + ); } - private ICredentials GetCredentials(bool valid = true, string accessToken = null) + private ICredentials GetCredentials(bool isValid) { var credentialsMock = new Mock(); + credentialsMock .Setup(mock => mock.AreValid()) - .Returns(valid); + .Returns(isValid); credentialsMock .SetupGet(mock => mock.AccessToken) - .Returns(accessToken); + .Returns(_accessToken); credentialsMock .SetupGet(mock => mock.TokenType) diff --git a/Bynder/Test/Api/RequestSender/HttpRequestSenderTest.cs b/Bynder/Test/Api/RequestSender/HttpRequestSenderTest.cs index 82bf28d..4d6c679 100644 --- a/Bynder/Test/Api/RequestSender/HttpRequestSenderTest.cs +++ b/Bynder/Test/Api/RequestSender/HttpRequestSenderTest.cs @@ -1,8 +1,10 @@ // Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. +using System.Linq; using System.Net; using System.Net.Http; +using System.Threading.Tasks; using Bynder.Sdk.Api.RequestSender; using Bynder.Test.Utils; using Xunit; @@ -12,16 +14,31 @@ namespace Bynder.Test.Api.RequestSender public class HttpRequestSenderTest { [Fact] - public void WhenErrorReceivedAnExceptionIsThown() + public async Task WhenSuccessReceivedResponseIsReturned() { - using (var testHttpListener = new TestHttpListener(HttpStatusCode.Forbidden, null)) + using (var httpListener = new TestHttpListener(HttpStatusCode.OK)) + using (var httpRequestSender = new HttpRequestSender()) { - using (HttpRequestSender apiRequestSender = new HttpRequestSender()) - { - HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, testHttpListener.ListeningUrl); + var requestMessage = new HttpRequestMessage(HttpMethod.Get, httpListener.ListeningUrl); + var response = await httpRequestSender.SendHttpRequest(requestMessage); - Assert.ThrowsAsync(async () => await apiRequestSender.SendHttpRequest(requestMessage)); - } + Assert.Equal( + httpRequestSender.UserAgent, + response.RequestMessage.Headers.GetValues("User-Agent").First() + ); + + } + } + [Fact] + public async Task WhenErrorReceivedAnExceptionIsThown() + { + using (var httpListener = new TestHttpListener(HttpStatusCode.Forbidden)) + using (var httpRequestSender = new HttpRequestSender()) + { + var requestMessage = new HttpRequestMessage(HttpMethod.Get, httpListener.ListeningUrl); + var doRequest = httpRequestSender.SendHttpRequest(requestMessage); + + await Assert.ThrowsAsync(() => doRequest); } } } diff --git a/Bynder/Test/Utils/TestHttpListener.cs b/Bynder/Test/Utils/TestHttpListener.cs index f1ba166..e389377 100644 --- a/Bynder/Test/Utils/TestHttpListener.cs +++ b/Bynder/Test/Utils/TestHttpListener.cs @@ -20,6 +20,11 @@ public sealed class TestHttpListener : IDisposable /// private readonly HttpListenerFactory _httpListenerFactory; + /// + /// Path of the file which contents will be the response to requests. + /// + private readonly string _responseContent; + /// /// Path of the file which contents will be the response to requests. /// @@ -37,11 +42,13 @@ public sealed class TestHttpListener : IDisposable /// Creates an instance of the class. /// /// HTTP status code that the class will return when having requests + /// String whose contents the class will return in the response /// File whose contents the class will return in the response - public TestHttpListener(HttpStatusCode statusCode, string responsePath) + public TestHttpListener(HttpStatusCode statusCode, string responseContent = null, string responsePath = null) { - _responsePath = responsePath; _statusCode = statusCode; + _responseContent = responseContent; + _responsePath = responsePath; _httpListenerFactory = new HttpListenerFactory(); @@ -97,7 +104,15 @@ private void SendResponse(HttpListenerResponse response) { response.StatusCode = (int)_statusCode; - if (!string.IsNullOrEmpty(_responsePath)) + if (!string.IsNullOrEmpty(_responseContent)) + { + using (var sw = new StreamWriter(response.OutputStream)) + { + sw.Write(_responseContent); + } + } + + else if (!string.IsNullOrEmpty(_responsePath)) { var dr = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); using (var fs = new FileStream(Path.Combine(dr, _responsePath), FileMode.Open, FileAccess.Read))