private async Task PutBoolAsync(IFlurlRequest url, bool value) { await url .WithHeader("Content-Type", "application/json") .PutJsonAsync(value) .ConfigureAwait(false); }
/// <inheritdoc /> public async Task <TSource?> FindAsync(string docId, bool withConflicts = false, CancellationToken cancellationToken = default) { try { IFlurlRequest request = NewRequest() .AppendPathSegment(docId); if (withConflicts) { request = request.SetQueryParam("conflicts", "true"); } TSource document = await request .GetJsonAsync <TSource>(cancellationToken) .SendRequestAsync() .ConfigureAwait(false); InitAttachments(document); return(document); } catch (CouchNotFoundException) { return(null); } }
internal static Task <IFlurlResponse> PatchMultipartAsync(this IFlurlRequest request, Action <CapturedMultipartContent> buildContent, CancellationToken cancellationToken = default(CancellationToken)) { var cmc = new CapturedMultipartContent(request.Settings); buildContent(cmc); return(request.SendAsync(new HttpMethod("PATCH"), cmc, cancellationToken)); }
public async Task EbayRestClient_Should_ThrowEbayException() { var moqAuth = Substitute.For <IOAuth2Authenticator>(); moqAuth.GetTokenAsync().Returns(new Token { AccessToken = "randomtoken" }); var restClient = new EbayRestClient { OAuth2Authenticator = moqAuth }; IFlurlRequest flurl = "/some/path" .AppendPathSegment($"to", fullyEncode: true) .WithHeader("header", "value"); using (var httpTest = new HttpTest()) { httpTest.RespondWith("Resource Not Found", 404); await Assert.ThrowsAsync <EbayException>(async() => await restClient.Request <object>(flurl)); } }
/// <inheritdoc /> public async Task <TSource> AddOrUpdateAsync(TSource document, bool batch = false, CancellationToken cancellationToken = default) { Check.NotNull(document, nameof(document)); if (string.IsNullOrEmpty(document.Id)) { throw new InvalidOperationException("Cannot add or update a document without an ID."); } IFlurlRequest request = NewRequest() .AppendPathSegment(document.Id); if (batch) { request = request.SetQueryParam("batch", "ok"); } DocumentSaveResponse response = await request .PutJsonAsync(document, cancellationToken) .ReceiveJson <DocumentSaveResponse>() .SendRequestAsync() .ConfigureAwait(false); document.ProcessSaveResponse(response); await UpdateAttachments(document, cancellationToken) .ConfigureAwait(false); return(document); }
public static async Task <ASMessageResponse> ToAngleSharpResponse( this IFlurlRequest flurlRequest, Func <IFlurlRequest, Task <HttpResponseMessage> > executionPredicate, CancellationToken cancellationToken = default ) { // conversion from *flurl's* custom URL to *anglesharp's* custom URL // apparently .NET's URI just sucks that much var uri = flurlRequest.Url.ToUri(); var url = Url.Convert(uri); if (executionPredicate == null) { Task <IFlurlResponse> flurlResponseMessageTask = flurlRequest.GetAsync(cancellationToken); var flurlResponseMessage = await flurlResponseMessageTask.ConfigureAwait(false); var content = await flurlResponseMessage.ResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); return(new ASMessageResponse(url, flurlResponseMessage.ResponseMessage, content)); } else { Task <HttpResponseMessage> httpResponseMessageTask = executionPredicate(flurlRequest); var httpResponseMessage = await httpResponseMessageTask.ConfigureAwait(false); var content = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); return(new ASMessageResponse(url, httpResponseMessage, content)); } }
public static async Task <T> GetSoundByteJsonAsync <T>(this IFlurlRequest req, IAuthenticationService authenticationService) { // Internal request var r = req; // If the users SoundByte account is connected, perform an OAuth request var accountToken = await authenticationService.GetSoundByteAccountTokenAsync(); if (accountToken != null) { r = r.WithOAuthBearerToken(accountToken.AccessToken); } try { // Attempt to get the response return(await r.GetJsonAsync <T>()); } catch (FlurlHttpException ex) { // If an account token was provided, and if (accountToken != null && ex.Call.HttpStatus == System.Net.HttpStatusCode.Unauthorized) { // Refresh the token and store it var newToken = await authenticationService.RefreshTokenAsync(accountToken.RefreshToken, null); await authenticationService.ConnectSoundByteAccountAsync(newToken); // Perform the request again return(await GetSoundByteJsonAsync <T>(req, authenticationService)); } throw ex; } }
/// <summary> /// Returns changes as they happen. A continuous feed stays open and connected to the database until explicitly closed. /// </summary> /// <remarks> /// To stop receiving changes call <c>Cancel()</c> on the <c>CancellationTokenSource</c> used to create the <c>CancellationToken</c>. /// </remarks> /// <param name="options">Options to apply to the request.</param> /// <param name="filter">A filter to apply to the result.</param> /// <param name="cancellationToken">A cancellation token to stop receiving changes.</param> /// <returns></returns> public async IAsyncEnumerable <ChangesFeedResponseResult <TSource> > GetContinuousChangesAsync(ChangesFeedOptions options, ChangesFeedFilter filter, [EnumeratorCancellation] CancellationToken cancellationToken) { var infiniteTimeout = TimeSpan.FromMilliseconds(Timeout.Infinite); IFlurlRequest request = NewRequest() .WithTimeout(infiniteTimeout) .AppendPathSegment("_changes") .SetQueryParam("feed", "continuous"); SetChangesFeedOptions(request, options); await using Stream stream = filter == null ? await request.GetStreamAsync(cancellationToken, HttpCompletionOption.ResponseHeadersRead) .ConfigureAwait(false) : await QueryContinuousWithFilterAsync(request, filter, cancellationToken) .ConfigureAwait(false); using var reader = new StreamReader(stream); while (!cancellationToken.IsCancellationRequested && !reader.EndOfStream) { if (cancellationToken.IsCancellationRequested) { continue; } var line = await reader.ReadLineAsync().ConfigureAwait(false); if (!string.IsNullOrEmpty(line)) { yield return(JsonConvert.DeserializeObject <ChangesFeedResponseResult <TSource> >(line)); } } }
public static Task <T> GetHtmlAsync <T>( this IFlurlRequest request, CancellationToken cancellationToken = default, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { return(request.SendAsync(HttpMethod.Get, null, cancellationToken, completionOption).ReceiveHtml <T>()); }
public IEnumerable <TResponse> Enumerate(IFlurlRequest request) { request.AllowHttpStatus(HttpStatusCode.NotFound); var more = true; while (more) { // Need headers & result so capture task first: https://stackoverflow.com/a/53514668/129269 var task = request.GetAsync(); var response = task.ConfigureAwait(false).GetAwaiter().GetResult(); if (response.StatusCode == HttpStatusCode.NotFound) { throw new NotFoundException(request.Url.ToString()); } var data = task.ReceiveJson <Multiple <TResponse> >().ConfigureAwait(false).GetAwaiter().GetResult(); foreach (var item in data.Value) { yield return(item); } more = response.Headers.TryGetValues("x-ms-continuationtoken", out var tokens); request.SetQueryParam("continuationToken", tokens?.First()); } }
/// <summary> /// Creates a new document and returns it. /// </summary> /// <param name="document">The document to create.</param> /// <param name="batch">Stores document in batch mode.</param> /// <returns>A task that represents the asynchronous operation. The task result contains the element created.</returns> public async Task <TSource> CreateAsync(TSource document, bool batch = false) { if (!string.IsNullOrEmpty(document.Id)) { return(await CreateOrUpdateAsync(document) .ConfigureAwait(false)); } IFlurlRequest request = NewRequest(); if (batch) { request = request.SetQueryParam("batch", "ok"); } DocumentSaveResponse response = await request .PostJsonAsync(document) .ReceiveJson <DocumentSaveResponse>() .SendRequestAsync() .ConfigureAwait(false); document.ProcessSaveResponse(response); await UpdateAttachments(document) .ConfigureAwait(false); return(document); }
/// <summary> /// This method is used to get a commit author's full name via the github rest api. /// Reference: https://docs.github.com/en/rest /// For an example response: https://api.github.com/repos/Azure/iotedge/commits/704250b /// </summary> /// <param name="commit">Commit for which to get the author's full name.</param> /// <returns>Full name of author.</returns> public async Task <string> GetAuthorFullNameFromCommitAsync(string commit) { string requestPath = string.Format(UserPathSegmentFormat, commit); IFlurlRequest workItemQueryRequest = ((Url)requestPath) .WithHeader("Content-Type", "application/json") .WithHeader("User-Agent", "Azure/iotedge"); JObject result; try { IFlurlResponse response = await workItemQueryRequest.GetAsync(); result = await response.GetJsonAsync <JObject>(); string fullName = result["commit"]["author"]["name"].ToString(); return(fullName); } catch (FlurlHttpException e) { string message = $"Failed making call to commit api: {e.Message}"; Console.WriteLine(message); Console.WriteLine(e.Call.RequestBody); Console.WriteLine(e.Call.Response.StatusCode); Console.WriteLine(e.Call.Response.ResponseMessage); throw new Exception(message); } }
public static async Task <CiatData> LoadData(Position position, string crop) { string lat = position.Latitude.ToString().Replace(",", "."); string lon = position.Longitude.ToString().Replace(",", "."); IFlurlRequest request = Constants .MatrizBaseUrl .SetQueryParams( new { lat = lat, lon = lon, type = "matriz", tkn = Constants.BemToken, cultivo = crop }) .WithBasicAuth(Constants.BemUsername, Constants.BemPassword); List <CiatResponseData> responseData; try { responseData = await request.GetJsonAsync <List <CiatResponseData> >(); } catch (Exception e) { return(null); } return(CiatData.FromResponse(responseData, request.Url)); }
/// <summary> /// Adds or updates a name-value pair in this request's Cookie header. /// To automatically maintain a cookie "session", consider using a CookieJar or CookieSession instead. /// </summary> /// <param name="request">The IFlurlRequest.</param> /// <param name="name">The cookie name.</param> /// <param name="value">The cookie value.</param> /// <returns>This IFlurlClient instance.</returns> public static IFlurlRequest WithCookie(this IFlurlRequest request, string name, object value) { var cookies = new NameValueList <string>(request.Cookies); cookies.AddOrReplace(name, value.ToInvariantString()); return(request.WithHeader("Cookie", CookieCutter.ToRequestHeader(cookies))); }
/// <summary> /// This method is used to get latest build result of given build definition Ids and branch name. /// The results should always contain same number of vsts build entity of given build definitions. /// If result is not found for a build definition Id, it will return vsts build entity with no result. /// Note: no validation of build definition ids is taken. /// Reference: https://docs.microsoft.com/en-us/rest/api/azure/devops/wit/work%20items/list?view=azure-devops-rest-5.1 /// </summary> /// <param name="buildDefinitionIds">build definition Ids</param> /// <param name="branchName">github repository branch name</param> /// <returns>List of vsts build entities</returns> public async Task <IList <VstsBuild> > GetLatestBuildsAsync(HashSet <BuildDefinitionId> buildDefinitionIds, string branchName) { ValidationUtil.ThrowIfNulOrEmptySet(buildDefinitionIds, nameof(buildDefinitionIds)); ValidationUtil.ThrowIfNullOrWhiteSpace(branchName, nameof(branchName)); // TODO: need to think about how to handle unexpected exception during REST API call string requestPath = string.Format(LatestBuildPathSegmentFormat, this.accessSetting.Organization, this.accessSetting.Project); IFlurlRequest latestBuildRequest = DevOpsAccessSetting.BaseUrl .AppendPathSegment(requestPath) .SetQueryParam("definitions", string.Join(",", buildDefinitionIds.Select(b => b.IdString()))) .SetQueryParam("queryOrder", "finishTimeDescending") .SetQueryParam("maxBuildsPerDefinition", "1") .SetQueryParam("api-version", "5.1") .SetQueryParam("branchName", branchName) .WithBasicAuth(string.Empty, this.accessSetting.PersonalAccessToken); string resultJson = await latestBuildRequest.GetStringAsync().ConfigureAwait(false); JObject result = JObject.Parse(resultJson); if (!result.ContainsKey("count") || (int)result["count"] <= 0) { return(buildDefinitionIds.Select(i => VstsBuild.GetBuildWithNoResult(i, branchName)).ToList()); } Dictionary <BuildDefinitionId, VstsBuild> latestBuilds = JsonConvert.DeserializeObject <VstsBuild[]>(result["value"].ToString()).ToDictionary(b => b.DefinitionId, b => b); return(buildDefinitionIds.Select(i => latestBuilds.ContainsKey(i) ? latestBuilds[i] : VstsBuild.GetBuildWithNoResult(i, branchName)).ToList()); }
/// <summary> /// Creates a new database with the given name in the server. /// The name must begin with a lowercase letter and can contains only lowercase characters, digits or _, $, (, ), +, - and /.s /// </summary> /// <typeparam name="TSource">The type of database documents.</typeparam> /// <param name="database">The database name.</param> /// <param name="shards">The number of range partitions. Default is 8, unless overridden in the cluster config.</param> /// <param name="replicas">The number of copies of the database in the cluster. The default is 3, unless overridden in the cluster config.</param> /// <returns>A task that represents the asynchronous operation. The task result contains the newly created CouchDB database.</returns> public async Task <CouchDatabase <TSource> > CreateDatabaseAsync <TSource>(string database, int?shards = null, int?replicas = null) where TSource : CouchDocument { database = EscapeDatabaseName(database); IFlurlRequest request = NewRequest() .AppendPathSegment(database); if (shards.HasValue) { request = request.SetQueryParam("q", shards.Value); } if (replicas.HasValue) { request = request.SetQueryParam("n", replicas.Value); } OperationResult result = await request .PutAsync(null) .ReceiveJson <OperationResult>() .SendRequestAsync() .ConfigureAwait(false); if (!result.Ok) { throw new CouchException("Something went wrong during the creation"); } return(new CouchDatabase <TSource>(_flurlClient, _settings, ConnectionString, database)); }
public static Url SetQueryModel <TType>(this IFlurlRequest url, TType model) { var properties = model.GetType().GetProperties(); Dictionary <string, object> dict = new Dictionary <string, object>(); foreach (var prop in properties) { var name = prop.Name.ToLower(); var value = prop.GetValue(model, null); var attrs = prop.GetCustomAttributes(false); foreach (var item in attrs) { if (item is FlurlPropertyAttribute castedItem) { name = castedItem.ParamName; } } if (value != null) { dict.Add(name, value); } } return(new Url(url.Url).SetQueryParams(dict)); }
/// <summary> /// This method is used to execute a Dev Ops work item query and get the number of bugs for a given query. /// If result is not found for a query, it will return 0. /// Reference: https://docs.microsoft.com/en-us/rest/api/azure/devops/wit/wiql/query%20by%20wiql?view=azure-devops-rest-5.1 /// </summary> /// <param name="bugQuery">Bug query object representing vsts shared queries</param> /// <returns>Number of bugs output by query</returns> public async Task <int> GetBugsCountAsync(BugQuery bugQuery) { // TODO: need to think about how to handle unexpected exception during REST API call string requestPath = string.Format(WorkItemPathSegmentFormat, DevOpsAccessSetting.BaseUrl, this.accessSetting.Organization, this.accessSetting.Project, this.accessSetting.Team); IFlurlRequest workItemQueryRequest = ((Url)requestPath) .WithBasicAuth(string.Empty, this.accessSetting.PersonalAccessToken) .SetQueryParam("api-version", "5.1"); JObject result; try { IFlurlResponse response = await workItemQueryRequest .PostJsonAsync(new { query = bugQuery.GetWiqlFromConfiguration() }); result = await response.GetJsonAsync <JObject>(); } catch (FlurlHttpException e) { Console.WriteLine($"Failed making call to vsts work item api: {e.Message}"); Console.WriteLine(e.Call.RequestBody); Console.WriteLine(e.Call.Response.StatusCode); Console.WriteLine(e.Call.Response.ResponseMessage); return(0); } if (!result.ContainsKey("queryType")) { return(0); } return(result["workItems"].Count()); }
public static async Task <string> HandleAuthExpireAsync(this IFlurlRequest req, IAuthenticationService authenticationService, MusicProvider musicProvider) { // Internal request var r = req; // If the music provider account is connected, perform an OAuth request var accountToken = await authenticationService.GetTokenAsync(musicProvider.Identifier); if (accountToken != null && musicProvider.Manifest.Authentication != null) { r = r.WithHeader("Authorization", $"{musicProvider.Manifest.Authentication.Scheme ?? "Bearer"} {accountToken.AccessToken}"); } try { // Attempt to get the response return(await r.GetStringAsync()); } catch (FlurlHttpException ex) { // If an account token was provided, and if (accountToken != null && ex.Call.HttpStatus == System.Net.HttpStatusCode.Unauthorized) { // Refresh the token and store it var newToken = await authenticationService.RefreshTokenAsync(accountToken.RefreshToken, musicProvider.Identifier); await authenticationService.ConnectAccountAsync(musicProvider.Identifier, newToken); // Perform the request again return(await HandleAuthExpireAsync(req, authenticationService, musicProvider)); } throw ex; } }
private static async Task <List <T> > Load <T>(string parameter, double?lat = null, double?lon = null, CropType?crop = null) { IFlurlRequest request = $"{Constants.BemBaseUrl}?type={parameter}&tkn={Constants.BemToken}" .WithBasicAuth(Constants.BemUsername, Constants.BemPassword); if (lat != null) { request.SetQueryParam("lat", lat.Value.ToString(CultureInfo.InvariantCulture)); } if (lon != null) { request.SetQueryParam("lon", lon.Value.ToString(CultureInfo.InvariantCulture)); } if (crop != null && crop == CropType.Corn) //TODO: use crop types { request.SetQueryParam("cultivo", "Maiz"); } List <T> data = null; try { data = await request.GetJsonAsync <List <T> >(); } catch (Exception) { } return(data); }
public static async Task <dynamic> PostRequest(string[] PathSegments, object RequestBodyObject) { IFlurlRequest request = TrackIt_2019.API.BaseUri.AppendPathSegments(PathSegments).WithOAuthBearerToken((string)TrackIt_2019.API.Authentication.Token.access_token); dynamic result = await request.PostJsonAsync(RequestBodyObject).ReceiveJson(); return(result); }
/// <summary> /// Sends an asynchronous multipart/form-data POST request. /// </summary> /// <param name="buildContent">A delegate for building the content parts.</param> /// <param name="request">The IFlurlRequest.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param> /// <returns>A Task whose result is the received HttpResponseMessage.</returns> public static Task <HttpResponseMessage> PostMultipartAsync(this IFlurlRequest request, Action <CapturedMultipartContent> buildContent, CancellationToken cancellationToken = default(CancellationToken)) { var cmc = new CapturedMultipartContent(request.Settings); buildContent(cmc); return(request.SendAsync(HttpMethod.Post, cmc, cancellationToken)); }
/// <inheritdoc /> public async Task <TSource> AddAsync(TSource document, bool batch = false, CancellationToken cancellationToken = default) { Check.NotNull(document, nameof(document)); if (!string.IsNullOrEmpty(document.Id)) { return(await AddOrUpdateAsync(document, batch, cancellationToken) .ConfigureAwait(false)); } IFlurlRequest request = NewRequest(); if (batch) { request = request.SetQueryParam("batch", "ok"); } DocumentSaveResponse response = await request .PostJsonAsync(document, cancellationToken) .ReceiveJson <DocumentSaveResponse>() .SendRequestAsync() .ConfigureAwait(false); document.ProcessSaveResponse(response); await UpdateAttachments(document, cancellationToken) .ConfigureAwait(false); return(document); }
private async Task <HttpResponseMessage> SendAuthorizedHttpPostRequestAsync <TEvent>(IEnumerable <TEvent> events) where TEvent : class { IFlurlRequest authorizedRequest = TopicEndpoint.WithHeader(name: "aeg-sas-key", value: _authenticationKey); return(await authorizedRequest.PostJsonAsync(events)); }
/// <inheritdoc /> public async IAsyncEnumerable <ChangesFeedResponseResult <TSource> > GetContinuousChangesAsync(ChangesFeedOptions options, ChangesFeedFilter filter, [EnumeratorCancellation] CancellationToken cancellationToken) { var infiniteTimeout = TimeSpan.FromMilliseconds(Timeout.Infinite); IFlurlRequest request = NewRequest() .WithTimeout(infiniteTimeout) .AppendPathSegment("_changes") .SetQueryParam("feed", "continuous"); if (options != null) { request = request.ApplyQueryParametersOptions(options); } await using Stream stream = filter == null ? await request.GetStreamAsync(cancellationToken, HttpCompletionOption.ResponseHeadersRead) .ConfigureAwait(false) : await request.QueryContinuousWithFilterAsync <TSource>(_queryProvider, filter, cancellationToken) .ConfigureAwait(false); await foreach (var line in stream.ReadLinesAsync(cancellationToken)) { if (string.IsNullOrEmpty(line)) { continue; } ChangesFeedResponseResult <TSource> result = JsonConvert.DeserializeObject <ChangesFeedResponseResult <TSource> >(line); yield return(result); } }
/// <summary> /// Create new API service with specific route /// </summary> /// <param name="route">Specific route </param> public APIService(string route = "") { try { #if DEBUG var httpClientHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true }; #endif var httpClient = new HttpClient(httpClientHandler) { BaseAddress = new Uri(Device.RuntimePlatform == Device.Android ? AppResources.ApiUrlAndroid : AppResources.ApiUrl) }; IFlurlClient flurlClient = new FlurlClient(httpClient); request = flurlClient.Request(route).AllowAnyHttpStatus(); if (Auth.IsAuthenticated(setLoginPage: false)) { request.Headers.Add("Authorization", $"Bearer {Auth.AccessToken.ConvertToString()}"); } BaseUrl = request.Url; } #pragma warning disable 168 catch (Exception ex) #pragma warning restore 168 { // ignored } }
/// <summary> /// Sends an asynchronous HTTP request. /// </summary> /// <param name="request">The request.</param> /// <param name="httpMethod">HTTP method of the request</param> /// <param name="data">Contents of the request body.</param> /// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation. Optional.</param> /// <param name="completionOption">The HttpCompletionOption used in the request. Optional.</param> /// <returns> /// A Task whose result is the received HttpResponseMessage. /// </returns> public static async Task <HttpResponseMessage> SendXmlAsync(this IFlurlRequest request, HttpMethod httpMethod, object data, CancellationToken cancellationToken = default(CancellationToken), HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { var content = new CapturedXmlContent(request.Settings.XmlSerializer().Serialize(data), request.GetMediaType()); return(await request.SendAsync(httpMethod, content, cancellationToken, completionOption)); }
private static async Task <IList <JToken> > GetJsonTokensAsync(this IFlurlRequest request, CancellationToken cancellationToken = default, HttpCompletionOption completionOption = HttpCompletionOption.ResponseContentRead) { var responseMessage = await request.SendAsync(HttpMethod.Get, null, cancellationToken, completionOption).ConfigureAwait(false); if (responseMessage == null) { return(default);
public static async Task <HttpResponseMessage> PostRequestAsync(this IFlurlRequest client, IRequest request, CancellationToken token = default(CancellationToken), bool isMultipart = false) { // Get all form keys from request var keys = request.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly) .Where(prop => prop.GetCustomAttribute <FormIgnoreAttribute>() == null); // Get determination key for determine if the web call is a v2 call or not var v2DetKey = keys.SingleOrDefault(prop => prop.GetCustomAttribute <V2DeterminationKeyAttribute>() != null); bool isV2 = v2DetKey != null && v2DetKey.GetValue(request) != null; if (isV2 && v2DetKey.GetCustomAttribute <V2DeterminationKeyAttribute>().IsByVersionNumber) { isV2 &= v2DetKey.GetValue(request).ToString() == "2.0"; } // Get keys to process var keysToProcess = keys.Except(keys.Where(k => k.GetCustomAttribute <FormAttribute>()?.IsFor.Equals(isV2 ? VersionOption.V1 : VersionOption.V2) ?? false)); // Check null for required keys foreach (var option in new List <VersionOption> { isV2?VersionOption.V2 : VersionOption.V1, VersionOption.All }) { var keysToValidate = keysToProcess.Where(k => k.GetCustomAttribute <FormAttribute>()?.IsFor.Equals(option) ?? false); var invalidKeys = keysToValidate.Where(k => (k.GetCustomAttribute <FormAttribute>()?.IsRequiredFor.HasFlag(option) ?? false) && k.GetValue(request) == null); if (invalidKeys.Any()) { throw new KeyRequiredException(invalidKeys.Select(k => k.Name).ToArray()); } } Func <PropertyInfo, string> keyName = prop => prop.GetCustomAttribute <FormAttribute>()?.Name ?? prop.Name.ToLower(); // Process keys to form var form = new Dictionary <string, string>(); keysToProcess.Where(ok => ok.GetValue(request) != null) .ToList() .ForEach(ok => { if (ok.GetValue(request) is IEnumerable <string> list) { foreach (var item in list) { form.Add(keyName(ok), item); } } else { var objValue = ok.GetValue(request); if (!(objValue is bool && ok.GetCustomAttribute <FormAttribute>().AddKeyOnlyIfBoolTrue&& !(bool)objValue)) { form.Add(keyName(ok), ok.PropertyType.IsSimpleType() ? objValue.ToString() : JsonConvert.SerializeObject(ok.GetValue(request), SerializationHelper.SerializationSettings)); } } }); // ConfigureAwait was missing... return(isMultipart ? await client.PostMultipartAsync(mp => mp.AddStringParts(form)).ConfigureAwait(false) : await client.PostUrlEncodedAsync(form, token).ConfigureAwait(false)); }
/// <summary> /// This method is used to get latest release result of given release definition Id and branch name with descending order. /// Reference: https://docs.microsoft.com/en-us/rest/api/azure/devops/release/releases/list?view=azure-devops-rest-5.1 /// </summary> /// <param name="definitionId">release definition Ids</param> /// <param name="branchName">github repository branch name</param> /// <returns>List of IoT Edge releases</returns> public async Task <List <IoTEdgeRelease> > GetReleasesAsync(ReleaseDefinitionId definitionId, string branchName, int top = 5) { ValidationUtil.ThrowIfNullOrWhiteSpace(branchName, nameof(branchName)); ValidationUtil.ThrowIfNonPositive(top, nameof(top)); // TODO: need to think about how to handle unexpected exception during REST API call string requestPath = string.Format(ReleasePathSegmentFormat, this.accessSetting.Organization, this.accessSetting.Project); IFlurlRequest latestBuildRequest = DevOpsAccessSetting.ReleaseManagementBaseUrl .AppendPathSegment(requestPath) .SetQueryParam("definitionId", definitionId.IdString()) .SetQueryParam("queryOrder", "descending") .SetQueryParam("$expand", "environments") .SetQueryParam("$top", top) .SetQueryParam("api-version", "5.1") .SetQueryParam("sourceBranchFilter", branchName) .WithBasicAuth(string.Empty, this.accessSetting.PersonalAccessToken); string resultJson = await latestBuildRequest.GetStringAsync().ConfigureAwait(false); JObject result = JObject.Parse(resultJson); if (!result.ContainsKey("count") || (int)result["count"] <= 0) { return(new List <IoTEdgeRelease>()); } VstsRelease[] releases = JsonConvert.DeserializeObject <VstsRelease[]>(result["value"].ToString()); return(releases.Select(r => IoTEdgeRelease.Create(r, branchName)).ToList()); }