From ada5b8dc26c2226fb424c0401b63a05d19a5c99d Mon Sep 17 00:00:00 2001 From: Quirijn Slings Date: Mon, 19 Aug 2024 10:58:23 +0200 Subject: [PATCH 01/10] targetframework now net8.0, packages upgraded --- Bynder/Sample/Bynder.Sample.csproj | 4 ++-- Bynder/Sdk/Bynder.Sdk.csproj | 6 +++--- Bynder/Test/Bynder.Test.csproj | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Bynder/Sample/Bynder.Sample.csproj b/Bynder/Sample/Bynder.Sample.csproj index 36371fd..2efc672 100644 --- a/Bynder/Sample/Bynder.Sample.csproj +++ b/Bynder/Sample/Bynder.Sample.csproj @@ -1,7 +1,7 @@ Exe - net5.0 + net8.0 2.0.0 2.0.0 Bynder @@ -9,7 +9,7 @@ Copyright © Bynder - + diff --git a/Bynder/Sdk/Bynder.Sdk.csproj b/Bynder/Sdk/Bynder.Sdk.csproj index da92461..fc3d907 100644 --- a/Bynder/Sdk/Bynder.Sdk.csproj +++ b/Bynder/Sdk/Bynder.Sdk.csproj @@ -1,6 +1,6 @@ - netstandard2.1;net48 + net8.0 2.2.12.0 2.2.12.0 Bynder @@ -25,8 +25,8 @@ true - - + + diff --git a/Bynder/Test/Bynder.Test.csproj b/Bynder/Test/Bynder.Test.csproj index 960e55f..94b52dc 100644 --- a/Bynder/Test/Bynder.Test.csproj +++ b/Bynder/Test/Bynder.Test.csproj @@ -1,6 +1,6 @@  - net5.0 + net8.0 true 2.0.0 2.0.0 @@ -9,15 +9,15 @@ Copyright © Bynder - runtime; build; native; contentfiles; analyzers; buildtransitive + runtime; build; native; contentfiles; analyzers; buildtransitive all - - + + - - - runtime; build; native; contentfiles; analyzers; buildtransitive + + + runtime; build; native; contentfiles; analyzers; buildtransitive all From 619fc0e6ea0e510bcb172973f159c76c60522a73 Mon Sep 17 00:00:00 2001 From: Quirijn Slings Date: Fri, 23 Aug 2024 17:06:59 +0200 Subject: [PATCH 02/10] Make it possible to find assets through a metaproperty query --- Bynder/Sample/ApiSample.cs | 8 ++ Bynder/Sample/MetaPropertyToMediaSample.cs | 118 ++++++++++++++++++ Bynder/Sdk/Query/Asset/MediaQuery.cs | 15 +++ Bynder/Sdk/Query/Decoder/QueryDecoder.cs | 5 +- Bynder/Sdk/Settings/Configuration.cs | 1 + Bynder/Test/Service/Asset/AssetServiceTest.cs | 42 +++++++ 6 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 Bynder/Sample/MetaPropertyToMediaSample.cs diff --git a/Bynder/Sample/ApiSample.cs b/Bynder/Sample/ApiSample.cs index 6c566c9..cc22626 100644 --- a/Bynder/Sample/ApiSample.cs +++ b/Bynder/Sample/ApiSample.cs @@ -48,6 +48,14 @@ public static async Task Main(string[] args) await MetapropertiesSample.MetapropertiesSampleAsync(); return; } + + // Run samples related to the relation between metaproperties and media items + if (args[0].Equals("MetapropertyToMediaSample")) { + Console.WriteLine("Running samples for metaproperties and related media..."); + await MetaPropertyToMediaSample.MetaPropertyToMediaSampleAsync(); + return; + } + // Run samples related to media if (args[0].Equals("MediaSample")) { Console.WriteLine("Running samples for media..."); diff --git a/Bynder/Sample/MetaPropertyToMediaSample.cs b/Bynder/Sample/MetaPropertyToMediaSample.cs new file mode 100644 index 0000000..bbaa232 --- /dev/null +++ b/Bynder/Sample/MetaPropertyToMediaSample.cs @@ -0,0 +1,118 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using Bynder.Sdk.Service; +using Bynder.Sample.Utils; +using Bynder.Sdk.Settings; +using System.Threading.Tasks; +using System.Linq; +using Bynder.Sdk.Query.Asset; +using Bynder.Sdk.Model; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; +using System.Text; + +namespace Bynder.Sample +{ + public class MetaPropertyToMediaSample + { + private IBynderClient _bynderClient; + + public static async Task MetaPropertyToMediaSampleAsync() + { + var configuration = Configuration.FromJson("Config.json"); + Console.WriteLine($"BaseUrl: {configuration.BaseUrl}"); + var apiSample = new MetaPropertyToMediaSample(configuration); + await apiSample.AuthenticateWithOAuth2Async( + useClientCredentials: configuration.RedirectUri == null + ); + await apiSample.RunMetaPropertyToMediaSampleAsync(); + } + + private MetaPropertyToMediaSample(Configuration configuration) { + _bynderClient = ClientFactory.Create(configuration); + } + + private async Task RunMetaPropertyToMediaSampleAsync() + { + var assetService = _bynderClient.GetAssetService(); + // Get a list of media with limit 10 + Console.WriteLine("Available metaproperties: "); + var metaProperties = await assetService.GetMetapropertiesAsync(); + int i = 0; + foreach(var metaProperty in metaProperties.Values) { + Console.WriteLine($"({++i}) MetaProperty {metaProperty.Name} ({metaProperty.Id})"); + } + Console.WriteLine("Enter number of the metaProperty to search by"); + var metaPropertyNr = Convert.ToInt32(Console.ReadLine()); + var metaPropertySelected = metaProperties.Skip(metaPropertyNr - 1).FirstOrDefault().Value; + i = 0; + foreach (var option in metaPropertySelected.Options) + { + Console.WriteLine($"({++i}) Option {option.Name} ({option.Id})"); + } + Console.WriteLine("Enter number of the option to search by, or a text value"); + var optionSearchText = Console.ReadLine(); + if (Int32.TryParse(optionSearchText, out int optionNr)) + { + var optionSelected = metaPropertySelected.Options.Skip(optionNr - 1).FirstOrDefault(); + optionSearchText = optionSelected.Name; + } + + // Get matching media (assets) + var mediaQuery = new MediaQuery() + { + MetaProperties = new Dictionary> { { metaPropertySelected.Name, [optionSearchText] } }, + Limit = 10 + }; + var mediaList = await _bynderClient.GetAssetService().GetMediaListAsync(mediaQuery); + foreach (var media in mediaList) + { + Console.WriteLine($"ID: {media.Id}"); + Console.WriteLine($"Name: {media.Name}"); + Console.WriteLine($"Meta properties: {ShowMetaProperties(media.PropertyOptionsDictionary)}"); + Console.WriteLine("-----------------"); + } + } + + private string ShowMetaProperties(Dictionary propertyOptionsDictionary) + { + if (propertyOptionsDictionary == null) + { + return ""; + } + StringBuilder sb = new StringBuilder(); + bool first = true; + foreach (var key in propertyOptionsDictionary.Keys) + { + if (first) + { + first = false; + } + else + { + sb.Append("|"); + } + sb.Append($"{key.Replace("property_","")}:{string.Join(',', propertyOptionsDictionary[key].Select(a => a.ToString()))}"); + } + return sb.ToString(); + } + + private async Task AuthenticateWithOAuth2Async(bool useClientCredentials) + { + if (useClientCredentials) + { + await _bynderClient.GetOAuthService().GetAccessTokenAsync(); + } + else + { + Browser.Launch(_bynderClient.GetOAuthService().GetAuthorisationUrl("state example")); + Console.WriteLine("Insert the code: "); + var code = Console.ReadLine(); + await _bynderClient.GetOAuthService().GetAccessTokenAsync(code); + } + } + + } +} diff --git a/Bynder/Sdk/Query/Asset/MediaQuery.cs b/Bynder/Sdk/Query/Asset/MediaQuery.cs index 8f40145..09b0869 100644 --- a/Bynder/Sdk/Query/Asset/MediaQuery.cs +++ b/Bynder/Sdk/Query/Asset/MediaQuery.cs @@ -72,5 +72,20 @@ public class MediaQuery /// [ApiField("propertyOptionId", Converter = typeof(ListConverter))] public IList PropertyOptionId { get; set; } = new List(); + + /// + /// Metaproperties + /// Look for assets by specifying meta properties and values by name + /// e.g. City: Amsterdam + /// + /// + /// - Use the database names of the metaproperties, not the IDs + /// - If the metaproperty is of type single/multiple select, the values should be the database names of the option(s) + /// - If the metaproperty is of type text, the values refer to the text itself + /// + /// + [ApiField("property_", Converter = typeof(MetapropertyOptionsConverter))] + public IDictionary> MetaProperties { get; set; } + } } diff --git a/Bynder/Sdk/Query/Decoder/QueryDecoder.cs b/Bynder/Sdk/Query/Decoder/QueryDecoder.cs index 345219b..c65dfce 100644 --- a/Bynder/Sdk/Query/Decoder/QueryDecoder.cs +++ b/Bynder/Sdk/Query/Decoder/QueryDecoder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Bynder. All rights reserved. +// Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System; @@ -67,7 +67,7 @@ private void ConvertProperty(PropertyInfo propertyInfo, object query, IDictionar { foreach (var item in dictConverter.Convert(value)) { - AddParam(parameters, $"{apiField.ApiName}.{item.Key}", item.Value); + AddParam(parameters, $"{apiField.ApiName}{item.Key}", item.Value); } } @@ -77,6 +77,7 @@ private void ConvertProperty(PropertyInfo propertyInfo, object query, IDictionar } } + private void AddParam(IDictionary parameters, string key, string value) { if (!string.IsNullOrEmpty(value)) diff --git a/Bynder/Sdk/Settings/Configuration.cs b/Bynder/Sdk/Settings/Configuration.cs index 9635042..cf8a355 100644 --- a/Bynder/Sdk/Settings/Configuration.cs +++ b/Bynder/Sdk/Settings/Configuration.cs @@ -54,6 +54,7 @@ public class Configuration /// instance public static Configuration FromJson(string filepath) { + Console.WriteLine("Read config from " + filepath); return JsonConvert.DeserializeObject(File.ReadAllText(filepath)); } } diff --git a/Bynder/Test/Service/Asset/AssetServiceTest.cs b/Bynder/Test/Service/Asset/AssetServiceTest.cs index 8b2d497..3f5200c 100644 --- a/Bynder/Test/Service/Asset/AssetServiceTest.cs +++ b/Bynder/Test/Service/Asset/AssetServiceTest.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http; using System.Threading.Tasks; using Bynder.Sdk.Api.Requests; using Bynder.Sdk.Api.RequestSender; using Bynder.Sdk.Model; using Bynder.Sdk.Query.Asset; +using Bynder.Sdk.Query.Decoder; using Bynder.Sdk.Service.Asset; using Moq; using Xunit; @@ -124,6 +126,46 @@ public async Task GetMediaListCallsRequestSenderWithValidRequest() Assert.Equal(result, mediaList); } + [Fact] + public async Task GetMediaListWithMetaPropertiesCallsRequestSenderWithValidRequest() + { + var result = new List(); + _apiRequestSenderMock.Setup(sender => sender.SendRequestAsync(It.IsAny>>())) + .ReturnsAsync(result); + var mediaQuery = new MediaQuery() { + MetaProperties = new Dictionary> { { "City", new[] { "Amsterdam", "Rotterdam" } } } + }; + var mediaList = await _assetService.GetMediaListAsync(mediaQuery); + + _apiRequestSenderMock.Verify(sender => sender.SendRequestAsync( + It.Is>>( + req => req.Path == "/api/v4/media/" + && req.HTTPMethod == HttpMethod.Get + && req.Query == mediaQuery + ) + )); + + Assert.Equal(result, mediaList); + } + + [Fact] + public async Task GetMediaListWithMetaPropertiesHasCorrectParameters() + { + var mediaQuery = new MediaQuery() + { + MetaProperties = new Dictionary> { { "City", new[] { "Amsterdam", "Rotterdam" } } } + }; + var mediaList = await _assetService.GetMediaListAsync(mediaQuery); + + QueryDecoder queryDecoder = new QueryDecoder(); + var parameters = queryDecoder.GetParameters(mediaQuery); + + Assert.True(parameters.ContainsKey("property_City")); + Assert.DoesNotContain(".", parameters["property_City"]); + Assert.Equal("Rotterdam", parameters["property_City"].Split(',').Last()); + } + + [Fact] public async Task GetDownloadFileUrlCallsRequestSenderWithValidRequest() { From a1e4a929ab7aad5f9d3007aef96f4775ded0d19e Mon Sep 17 00:00:00 2001 From: Quirijn Slings Date: Mon, 2 Sep 2024 11:03:30 +0200 Subject: [PATCH 03/10] Get full list of assets including count --- Bynder/Sample/MediaSample.cs | 4 +++ Bynder/Sdk/Model/MediaFullResult.cs | 22 ++++++++++++ Bynder/Sdk/Query/Asset/MediaQueryFull.cs | 19 +++++++++++ Bynder/Sdk/Service/Asset/AssetService.cs | 34 +++++++++++++++++++ Bynder/Sdk/Service/Asset/IAssetService.cs | 8 +++++ Bynder/Test/Service/Asset/AssetServiceTest.cs | 22 ++++++++++++ 6 files changed, 109 insertions(+) create mode 100644 Bynder/Sdk/Model/MediaFullResult.cs create mode 100644 Bynder/Sdk/Query/Asset/MediaQueryFull.cs diff --git a/Bynder/Sample/MediaSample.cs b/Bynder/Sample/MediaSample.cs index 501d496..a6e1398 100644 --- a/Bynder/Sample/MediaSample.cs +++ b/Bynder/Sample/MediaSample.cs @@ -40,6 +40,10 @@ private async Task RunMediaSampleAsync() Console.WriteLine($"Media Name: {media.Name}"); } + // Get ths same list as a full result + var mediaFullResult = await _bynderClient.GetAssetService().GetMediaFullResultAsync(new MediaQuery { Limit = 10 }); + Console.WriteLine($"Retrieving full result based on same query, total number of matching assets is {mediaFullResult.Total.Count}"); + // Get the media info Console.WriteLine("Enter the media ID to get the media info for: "); var mediaIdForInfo = Console.ReadLine(); diff --git a/Bynder/Sdk/Model/MediaFullResult.cs b/Bynder/Sdk/Model/MediaFullResult.cs new file mode 100644 index 0000000..463bb08 --- /dev/null +++ b/Bynder/Sdk/Model/MediaFullResult.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Bynder.Sdk.Model +{ + public class MediaFullResult + { + [JsonProperty("total")] + public Total Total { get; set; } + + [JsonProperty("media")] + public IList Media { get; set; } + } + public class Total + { + public long Count { get; set; } + } +} diff --git a/Bynder/Sdk/Query/Asset/MediaQueryFull.cs b/Bynder/Sdk/Query/Asset/MediaQueryFull.cs new file mode 100644 index 0000000..550184e --- /dev/null +++ b/Bynder/Sdk/Query/Asset/MediaQueryFull.cs @@ -0,0 +1,19 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using Bynder.Sdk.Model; +using Bynder.Sdk.Api.Converters; +using Bynder.Sdk.Query.Decoder; + +namespace Bynder.Sdk.Query.Asset +{ + /// + /// Query to filter media results, including the option to see the total number of results + /// + public class MediaQueryFull : MediaQuery + { + [ApiField("total")] + public bool Total { get; set; } + } +} diff --git a/Bynder/Sdk/Service/Asset/AssetService.cs b/Bynder/Sdk/Service/Asset/AssetService.cs index f94f779..f640327 100644 --- a/Bynder/Sdk/Service/Asset/AssetService.cs +++ b/Bynder/Sdk/Service/Asset/AssetService.cs @@ -232,5 +232,39 @@ public async Task DeleteAssetUsage(AssetUsageQuery query) Query = query }).ConfigureAwait(false); } + + /// + /// Check for more information + /// + /// Check for more information + public async Task GetMediaFullResultAsync(MediaQuery query) + { + var mediaQueryFull = query is MediaQueryFull ? query as MediaQueryFull : CloneIntoFullMediaQuery(query); + return await _requestSender.SendRequestAsync(new ApiRequest + { + Path = "/api/v4/media/", + HTTPMethod = HttpMethod.Get, + Query = mediaQueryFull, + }).ConfigureAwait(false); + } + + private static MediaQueryFull CloneIntoFullMediaQuery(MediaQuery query) + { + return new MediaQueryFull() + { + BrandId = query.BrandId, + CategoryId = query.CategoryId, + CollectionId = query.CollectionId, + Ids = query.Ids, + Keyword = query.Keyword, + Limit = query.Limit, + MetaProperties = query.MetaProperties, + Page = query.Page, + PropertyOptionId = query.PropertyOptionId, + SubBrandId = query.SubBrandId, + Type = query.Type, + Total = true + }; + } } } diff --git a/Bynder/Sdk/Service/Asset/IAssetService.cs b/Bynder/Sdk/Service/Asset/IAssetService.cs index 8f0a7d8..afa8425 100644 --- a/Bynder/Sdk/Service/Asset/IAssetService.cs +++ b/Bynder/Sdk/Service/Asset/IAssetService.cs @@ -122,5 +122,13 @@ public interface IAssetService /// Can be thrown when requests to server can't be completed or HTTP code returned by server is an error Task DeleteAssetUsage(AssetUsageQuery query); + /// + /// Get a full list of Bynder assets including the total number of matching results + /// + /// Information to correctly filter/paginate media + /// Task representing the full result, including the total number of matches + /// Can be thrown when requests to server can't be completed or HTTP code returned by server is an error + /// This method can be used to implement pagination in your app. The MediaFullResult that gets returned has a Total.Count property, which contains the total number of matching assets, not just the number of assets in the current result page + Task GetMediaFullResultAsync(MediaQuery query); } } diff --git a/Bynder/Test/Service/Asset/AssetServiceTest.cs b/Bynder/Test/Service/Asset/AssetServiceTest.cs index 3f5200c..e881e29 100644 --- a/Bynder/Test/Service/Asset/AssetServiceTest.cs +++ b/Bynder/Test/Service/Asset/AssetServiceTest.cs @@ -263,6 +263,28 @@ public async Task AddTagToMediaCallsRequestSenderWithValidRequest() )); } + [Fact] + public async Task GetMediaFullResultAsyncCallsRequestSenderWithValidRequest() + { + var result = new MediaFullResult() { Media = [ new Media() { Id = "SomeId", Name = "SomeName"} ] }; + _apiRequestSenderMock.Setup(sender => sender.SendRequestAsync(It.IsAny>())) + .ReturnsAsync(() => + { + return result; + }); + var mediaQuery = new MediaQuery(); + var mediaFullResult = await _assetService.GetMediaFullResultAsync(mediaQuery); + + _apiRequestSenderMock.Verify(sender => sender.SendRequestAsync( + It.Is>( + req => req.Path == "/api/v4/media/" + && req.HTTPMethod == HttpMethod.Get + && req.Query is MediaQueryFull + ) + )); + Assert.Equal(result, mediaFullResult); + } + [Fact] public async Task CreateAssetUsageCallsRequestSenderWithValidRequest() { From 19fe12b292a0dcb7bfb15b31a601f8601c47e269 Mon Sep 17 00:00:00 2001 From: Quirijn Slings Date: Wed, 4 Sep 2024 09:22:05 +0200 Subject: [PATCH 04/10] updated gitignore to the toptotal standard, added launchSettings to help developers run the samples --- .gitignore | 406 ++++++++++++++++++- Bynder/Sample/Properties/launchSettings.json | 40 ++ 2 files changed, 437 insertions(+), 9 deletions(-) create mode 100644 Bynder/Sample/Properties/launchSettings.json diff --git a/.gitignore b/.gitignore index 0250353..d1d96e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,405 @@ -# General macOS -.DS_Store -.AppleDouble -.LSOverride +# Created by https://www.toptal.com/developers/gitignore/api/visualstudio +# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudio + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* # Build results -bin/ -obj/ +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ -# The packages folder can be ignored because of Package Restore -packages/* +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info # Visual Studio code coverage results -coverage.opencover.xml +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +### VisualStudio Patch ### +# Additional files built by Visual Studio + +# End of https://www.toptal.com/developers/gitignore/api/visualstudio \ No newline at end of file diff --git a/Bynder/Sample/Properties/launchSettings.json b/Bynder/Sample/Properties/launchSettings.json new file mode 100644 index 0000000..865d3b8 --- /dev/null +++ b/Bynder/Sample/Properties/launchSettings.json @@ -0,0 +1,40 @@ +{ + "profiles": { + "WSL": { + "commandName": "WSL2", + "distributionName": "" + }, + "Run MediaSample": { + "commandName": "Project", + "commandLineArgs": "MediaSample" + }, + "Run MetapropertyToMediaSample": { + "commandName": "Project", + "commandLineArgs": "MetapropertyToMediaSample" + }, + "Run BrandsSample": { + "commandName": "Project", + "commandLineArgs": "BrandsSample" + }, + "Run MetapropertiesSample": { + "commandName": "Project", + "commandLineArgs": "MetapropertiesSample" + }, + "Run CollectionsSample": { + "commandName": "Project", + "commandLineArgs": "CollectionsSample" + }, + "Run TagsSample": { + "commandName": "Project", + "commandLineArgs": "TagsSample" + }, + "Run UploadSample": { + "commandName": "Project", + "commandLineArgs": "UploadSample" + }, + "Run AssetUsageSample": { + "commandName": "Project", + "commandLineArgs": "AssetUsageSample" + }, + } +} \ No newline at end of file From ac3c7e34658d10e59d8b064533ec12eadb3f9137 Mon Sep 17 00:00:00 2001 From: Quirijn Slings Date: Wed, 4 Sep 2024 14:46:44 +0200 Subject: [PATCH 05/10] Specify original filename, support custom parameters, include more asset properties during upload, upload file as stream --- Bynder/Sample/UploadSample.cs | 74 ++++++++++++++++++++++- Bynder/Sdk/Query/Upload/SaveMediaQuery.cs | 27 ++++++++- Bynder/Sdk/Query/Upload/UploadQuery.cs | 44 +++++++++++++- Bynder/Sdk/Service/Asset/AssetService.cs | 14 +++++ Bynder/Sdk/Service/Asset/IAssetService.cs | 15 ++++- Bynder/Sdk/Service/Upload/FileUploader.cs | 72 +++++++++++++++++----- 6 files changed, 227 insertions(+), 19 deletions(-) diff --git a/Bynder/Sample/UploadSample.cs b/Bynder/Sample/UploadSample.cs index 826279f..21a054a 100644 --- a/Bynder/Sample/UploadSample.cs +++ b/Bynder/Sample/UploadSample.cs @@ -8,6 +8,8 @@ using System.Threading.Tasks; using System.Linq; using Bynder.Sdk.Query.Upload; +using System.Collections.Generic; +using System.IO; namespace Bynder.Sample { public class UploadSample @@ -41,9 +43,77 @@ private async Task RunUploadSampleAsync() return; } - await assetService.UploadFileAsync(new UploadQuery { Filepath = uploadPath, BrandId = brands.First().Id }); + Console.WriteLine("Name of the media item after upload: "); + var name = Console.ReadLine(); + if (string.IsNullOrEmpty(name)) + { + name = null; + } + + Console.WriteLine("Override original filename (leave empty to use the actual filename): "); + var filename = Console.ReadLine(); + + Console.WriteLine("Description (leave empty to use default): "); + var description = Console.ReadLine(); + + var customParameters = GetCustomParameters(); + + Console.WriteLine("Do you want to pass a file stream to the SDK?: (y/n)"); + var passAsStream = Console.ReadLine().ToLower().StartsWith("y"); + + + var query = new UploadQuery + { + Filepath = uploadPath, + BrandId = brands.First().Id, + Name = name, + CustomParameters = customParameters + }; + if (!string.IsNullOrEmpty(filename)) + { + query.OriginalFileName = filename; + } + if (!string.IsNullOrEmpty(description)) + { + query.Description = description; + } + + FileStream fileStream = null; + if (passAsStream) + { + fileStream = File.Open(query.Filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); + } + var before = DateTime.Now; + var response = passAsStream ? await assetService.UploadFileAsync(fileStream, query) : await assetService.UploadFileAsync(query); + var ms = Math.Round((DateTime.Now - before).TotalMilliseconds); + Console.WriteLine($"Uploaded file as media with id {response.MediaId} (time elapsed: {ms})"); + } - + + private Dictionary GetCustomParameters() + { + Console.WriteLine("Do you want to add custom parameters during the upload? (y/n)"); + var input = Console.ReadLine(); + + if (!input.ToString().ToLower().StartsWith("y")) + { + return null; + } + + Dictionary parameters = new Dictionary(); + while (input.ToString().ToLower().StartsWith("y")) + { + Console.WriteLine("Parameter name: "); + var paramName = Console.ReadLine(); + Console.WriteLine("Parameter value: "); + var paramValue = Console.ReadLine(); + parameters.Add(paramName, paramValue); + Console.WriteLine("Do you want to add another custom parameter? (y/n)"); + input = Console.ReadLine(); + } + return parameters; + } + private async Task AuthenticateWithOAuth2Async(bool useClientCredentials) { if (useClientCredentials) diff --git a/Bynder/Sdk/Query/Upload/SaveMediaQuery.cs b/Bynder/Sdk/Query/Upload/SaveMediaQuery.cs index 0d8e01b..8e241fb 100644 --- a/Bynder/Sdk/Query/Upload/SaveMediaQuery.cs +++ b/Bynder/Sdk/Query/Upload/SaveMediaQuery.cs @@ -1,4 +1,4 @@ -// Copyright (c) Bynder. All rights reserved. +// Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Collections.Generic; @@ -43,6 +43,31 @@ internal class SaveMediaQuery [ApiField("tags", Converter = typeof(ListConverter))] public IList Tags { get; set; } + + /// + /// Description of the media + /// + [ApiField("description")] + public string Description { get; set; } + + /// + /// Published date of the media + /// + [ApiField("ISOPublicationDate")] + public string PublishedDate { get; set; } + + /// + /// Copyright information for the media + /// + [ApiField("copyright")] + public string Copyright { get; set; } + + /// + /// Indicates if the media is public + /// + [ApiField("isPublic")] + public bool IsPublic { get; set; } + /// /// Metaproperty options to set on the asset. /// diff --git a/Bynder/Sdk/Query/Upload/UploadQuery.cs b/Bynder/Sdk/Query/Upload/UploadQuery.cs index df69753..c3e2205 100644 --- a/Bynder/Sdk/Query/Upload/UploadQuery.cs +++ b/Bynder/Sdk/Query/Upload/UploadQuery.cs @@ -1,4 +1,4 @@ -// Copyright (c) Bynder. All rights reserved. +// Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System.Collections.Generic; @@ -15,6 +15,16 @@ public class UploadQuery /// public string Filepath { get; set; } + /// + /// Name the media will have. + /// + public string Name { get; set; } + + /// + /// Original file name the media will have. + /// + public string OriginalFileName { get; set; } + /// /// Brand id where we want to store the file /// @@ -31,5 +41,37 @@ public class UploadQuery /// Tags of the file that we want to update /// public IList Tags { get; set; } + + /// + /// Description of the media + /// + public string Description { get; set; } + + /// + /// Copyright information for the media + /// + public string Copyright { get; set; } + + /// + /// Indicates if the media is public + /// + public bool IsPublic { get; set; } + + /// + /// Metaproperties the media will have + /// + public IDictionary> MetapropertyOptions { get; set; } = new Dictionary>(); + + /// + /// Published date the media will have. + /// + public string PublishedDate { get; set; } + + /// + /// Custom parameters to add to the upload endpoint + /// + public IEnumerable> CustomParameters { get; set; } + + } } diff --git a/Bynder/Sdk/Service/Asset/AssetService.cs b/Bynder/Sdk/Service/Asset/AssetService.cs index f640327..ef16019 100644 --- a/Bynder/Sdk/Service/Asset/AssetService.cs +++ b/Bynder/Sdk/Service/Asset/AssetService.cs @@ -11,6 +11,7 @@ using Bynder.Sdk.Model; using Bynder.Sdk.Query.Asset; using Bynder.Sdk.Query.Upload; +using System.IO; namespace Bynder.Sdk.Service.Asset { @@ -143,6 +144,18 @@ public async Task UploadFileAsync(UploadQuery query) return await _uploader.UploadFileAsync(query).ConfigureAwait(false); } + /// + /// Check for more information + /// + /// Check for more information + /// Check for more information + /// Check for more information + public async Task UploadFileAsync(FileStream fileStream, UploadQuery query) + { + return await _uploader.UploadFileAsync(fileStream, query).ConfigureAwait(false); + } + + /// /// Check for more information /// @@ -266,5 +279,6 @@ private static MediaQueryFull CloneIntoFullMediaQuery(MediaQuery query) Total = true }; } + } } diff --git a/Bynder/Sdk/Service/Asset/IAssetService.cs b/Bynder/Sdk/Service/Asset/IAssetService.cs index afa8425..50ce12c 100644 --- a/Bynder/Sdk/Service/Asset/IAssetService.cs +++ b/Bynder/Sdk/Service/Asset/IAssetService.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using Bynder.Sdk.Model; using Bynder.Sdk.Query.Asset; @@ -74,7 +75,7 @@ public interface IAssetService Task> GetMediaListAsync(MediaQuery query); /// - /// Uploads a file async. + /// Uploads a file based on a filepath in the query /// /// Information to upload a file /// Task representing the upload @@ -82,6 +83,18 @@ public interface IAssetService /// Can be thrown when upload does not finish within expected time Task UploadFileAsync(UploadQuery query); + /// + /// Uploads a file as a stream + /// + /// Stream representing the file to be uploaded + /// Information to upload a file + /// Task representing the upload + /// Can be thrown when requests to server can't be completed or HTTP code returned by server is an error + /// Can be thrown when upload does not finish within expected time + + Task UploadFileAsync(FileStream fileStream, UploadQuery query); + + /// /// Modifies a media /// diff --git a/Bynder/Sdk/Service/Upload/FileUploader.cs b/Bynder/Sdk/Service/Upload/FileUploader.cs index cccc5fb..58b4aad 100644 --- a/Bynder/Sdk/Service/Upload/FileUploader.cs +++ b/Bynder/Sdk/Service/Upload/FileUploader.cs @@ -10,6 +10,8 @@ using Bynder.Sdk.Api.RequestSender; using Bynder.Sdk.Model; using Bynder.Sdk.Query.Upload; +using System.Linq; +using System.Web; namespace Bynder.Sdk.Service.Upload { @@ -69,6 +71,17 @@ public static FileUploader Create(IApiRequestSender requestSender) return new FileUploader(requestSender, new AmazonApi()); } + /// + /// Uploads a file with the data specified in query parameter + /// + /// Stream of the file to upload + /// Upload query information to upload a file + /// Task representing the upload + public async Task UploadFileAsync(Stream fileStream, UploadQuery query) + { + return await UploadFileAsync(fileStream, query, query.OriginalFileName ?? Path.GetFileName(query.Filepath)); + } + /// /// Uploads a file with the data specified in query parameter /// @@ -76,44 +89,66 @@ public static FileUploader Create(IApiRequestSender requestSender) /// Task representing the upload public async Task UploadFileAsync(UploadQuery query) { - var uploadRequest = await RequestUploadInformationAsync(new RequestUploadQuery { Filename = query.Filepath }).ConfigureAwait(false); + var filename = !string.IsNullOrEmpty(query.OriginalFileName) ? query.OriginalFileName : Path.GetFileName(query.Filepath); + var uploadRequest = await RequestUploadInformationAsync(new RequestUploadQuery { Filename = filename }).ConfigureAwait(false); uint chunkNumber = 0; - using (var file = File.Open(query.Filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete)) + var fileStream = File.Open(query.Filepath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); + return await UploadFileAsync(fileStream, query, filename); + + + } + + private async Task GetUploadRequest(string fileName) + { + return await RequestUploadInformationAsync(new RequestUploadQuery { Filename = fileName }).ConfigureAwait(false); + } + + + private async Task UploadFileAsync(Stream fileStream, UploadQuery query, string filename) + { + uint chunkNumber = 0; + var uploadRequest = await GetUploadRequest(filename); + using (fileStream) { int bytesRead = 0; var buffer = new byte[CHUNK_SIZE]; - long numberOfChunks = (file.Length + CHUNK_SIZE - 1) / CHUNK_SIZE; + long numberOfChunks = (fileStream.Length + CHUNK_SIZE - 1) / CHUNK_SIZE; - while ((bytesRead = file.Read(buffer, 0, buffer.Length)) > 0) + while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) > 0) { ++chunkNumber; await UploadPartAsync(Path.GetFileName(query.Filepath), buffer, bytesRead, chunkNumber, uploadRequest, (uint)numberOfChunks).ConfigureAwait(false); } } - var finalizeResponse = await FinalizeUploadAsync(uploadRequest, chunkNumber).ConfigureAwait(false); + var finalizeResponse = await FinalizeUploadAsync(uploadRequest, chunkNumber, query.CustomParameters).ConfigureAwait(false); if (await HasFinishedSuccessfullyAsync(finalizeResponse).ConfigureAwait(false)) { return await SaveMediaAsync(new SaveMediaQuery { - Filename = query.Filepath, + Filename = query.Name ?? query.Filepath, BrandId = query.BrandId, ImportId = finalizeResponse.ImportId, MediaId = query.MediaId, - Tags = query.Tags + Tags = query.Tags, + Description = query.Description, + Copyright = query.Copyright, + IsPublic = query.IsPublic, + MetapropertyOptions = query.MetapropertyOptions, + PublishedDate = query.PublishedDate }).ConfigureAwait(false); } else { - throw new BynderUploadException("Converter did not finished. Upload not completed"); + throw new BynderUploadException("Converter did not finish. Upload not completed"); } } /// - /// Gets the closes s3 endpoint. This is needed to know to which bucket Url it uploads chunks + /// Gets the closest s3 endpoint. This is needed to know to which bucket Url it uploads chunks /// /// Task containting string with the Url private async Task GetClosestS3EndpointAsync() @@ -243,7 +278,7 @@ private async Task HasFinishedSuccessfullyAsync(FinalizeResponse finalizeR } /// - /// Registers a chunk in Bynder using . + /// Registers a chunk in Bynder using . /// /// Upload request information /// Current chunk number @@ -274,7 +309,7 @@ private async Task RegisterChunkAsync(UploadRequest uploadRequest, uint chunkNum /// Requests information to start a new upload /// /// Contains the information needed to request upload information - /// Task containing information + /// Task containing information private async Task RequestUploadInformationAsync(RequestUploadQuery query) { var request = new ApiRequest @@ -288,12 +323,12 @@ private async Task RequestUploadInformationAsync(RequestUploadQue } /// - /// Finalizes an upload using . + /// Finalizes an upload using . /// /// Upload request information /// chunk number /// Task with information - private async Task FinalizeUploadAsync(UploadRequest uploadRequest, uint chunkNumber) + private async Task FinalizeUploadAsync(UploadRequest uploadRequest, uint chunkNumber, IEnumerable> customParameters) { var query = new FinalizeUploadQuery { @@ -302,9 +337,18 @@ private async Task FinalizeUploadAsync(UploadRequest uploadReq S3Filename = $"{uploadRequest.S3Filename}/p{chunkNumber}", Chunks = chunkNumber.ToString() }; + var requestParameters = ""; + if (customParameters != null) + { + requestParameters = string.Join('&', customParameters.Select(p => HttpUtility.UrlEncode(p.Key) + "=" + HttpUtility.UrlEncode(p.Value))); + if (!string.IsNullOrEmpty(requestParameters)) + { + requestParameters = "?" + requestParameters; + } + } var request = new ApiRequest { - Path = $"/api/v4/upload/{query.UploadId}/", + Path = $"/api/v4/upload/{query.UploadId}/{requestParameters}", HTTPMethod = HttpMethod.Post, Query = query }; From 193cf87424e75931c88f10d7293c42a62dc108ab Mon Sep 17 00:00:00 2001 From: Quirijn Slings Date: Thu, 19 Sep 2024 09:46:15 +0200 Subject: [PATCH 06/10] added separate sample class to test the new ways of finding assets --- Bynder/Sample/ApiSample.cs | 7 + Bynder/Sample/FindMediaSample.cs | 163 +++++++++++++++++++ Bynder/Sample/MediaSample.cs | 10 +- Bynder/Sample/Properties/launchSettings.json | 4 + 4 files changed, 183 insertions(+), 1 deletion(-) create mode 100644 Bynder/Sample/FindMediaSample.cs diff --git a/Bynder/Sample/ApiSample.cs b/Bynder/Sample/ApiSample.cs index 84546d9..837f4b8 100644 --- a/Bynder/Sample/ApiSample.cs +++ b/Bynder/Sample/ApiSample.cs @@ -63,6 +63,13 @@ public static async Task Main(string[] args) return; } + // Run samples related to finding media + if (args[0].Equals("FindMediaSample")) + { + Console.WriteLine("Running samples for find media..."); + await FindMediaSample.MediaSampleAsync(); + return; + } // Run samples related to modifying media if (args[0].Equals("ModifyMediaSample")) { diff --git a/Bynder/Sample/FindMediaSample.cs b/Bynder/Sample/FindMediaSample.cs new file mode 100644 index 0000000..7cd3da0 --- /dev/null +++ b/Bynder/Sample/FindMediaSample.cs @@ -0,0 +1,163 @@ +// Copyright (c) Bynder. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. + +using System; +using Bynder.Sdk.Service; +using Bynder.Sample.Utils; +using Bynder.Sdk.Settings; +using System.Threading.Tasks; +using System.Linq; +using Bynder.Sdk.Query.Asset; +using Bynder.Sdk.Model; +using System.Collections.Generic; + +namespace Bynder.Sample +{ + public class FindMediaSample + { + private IBynderClient _bynderClient; + + public static async Task MediaSampleAsync() + { + var configuration = Configuration.FromJson("Config.json"); + var apiSample = new FindMediaSample(configuration); + await apiSample.AuthenticateWithOAuth2Async( + useClientCredentials: configuration.RedirectUri == null + ); + await apiSample.RunFindMediaSampleAsync(); + } + + private FindMediaSample(Configuration configuration) + { + _bynderClient = ClientFactory.Create(configuration); + } + + private async Task RunFindMediaSampleAsync() + { + var metaProperties = await _bynderClient.GetAssetService().GetMetapropertiesAsync(); + bool c = true; + while (c) + { + await PerformSearch(metaProperties); + Console.WriteLine("Do you want to perform another search? (y/N)"); + var inp = Console.ReadLine(); + c = inp.ToLower().StartsWith("y"); + } + } + + private async Task PerformSearch(IDictionary metaProperties) + { + Console.WriteLine("You have the following meta properties in your Bynder environment: "); + var counter = 1; + foreach (var metaProperty in metaProperties) + { + var extraInfo = metaProperty.Value.Options?.Any() ?? false ? $"[with {metaProperty.Value.Options.Count()} options]" : "[without options]"; + Console.WriteLine($"{counter++}) {metaProperty.Key} {extraInfo}"); + } + Console.WriteLine("Type the number of the meta property to perform a search with: "); + var mpNrInput = Console.ReadLine(); + if (!int.TryParse(mpNrInput, out int mpNr)) + { + mpNr = 1; + } + var selectedMetaProperty = metaProperties.Skip(mpNr - 1).FirstOrDefault().Value; + if (selectedMetaProperty == null) + { + Console.WriteLine("No meta property found, stopping execution"); + + return ; + } + + string searchString = null; + if (selectedMetaProperty.Options?.Any() ?? false) + { + counter = 1; + foreach (var option in selectedMetaProperty.Options) + { + Console.WriteLine($"{counter++}) {option.Label}"); + } + Console.WriteLine("Type the number of the option to search for: "); + mpNrInput = Console.ReadLine(); + if (!int.TryParse(mpNrInput, out mpNr)) + { + mpNr = 1; + } + var selectedOption = selectedMetaProperty.Options.Skip(mpNr - 1).FirstOrDefault(); + searchString = selectedOption.Name; + + Console.WriteLine("Searching via the meta property"); + var assets = await _bynderClient.GetAssetService().GetMediaListAsync(new MediaQuery() + { + MetaProperties = new Dictionary> + { + { + selectedMetaProperty.Name, [ searchString ] + } + } + }); + + if (assets?.Any() ?? false) + { + Console.WriteLine($"Found {assets.Count} assets, showing first 5"); + counter = 1; + foreach (var asset in assets) + { + Console.WriteLine($"{counter++}) {asset.Name}"); + if (counter == 6) + { + break; + } + } + } + else + { + Console.WriteLine("No assets found by metaproperty"); + } + } + else + { + Console.WriteLine("String to search for: "); + searchString = Console.ReadLine(); + } + Console.WriteLine("Searching by keyword"); + var assetsByKeyword = await _bynderClient.GetAssetService().GetMediaListAsync(new MediaQuery() + { + Keyword = searchString + } + ); + if (assetsByKeyword?.Any() ?? false) + { + Console.WriteLine($"Found {assetsByKeyword.Count} assets, showing first 5"); + counter = 1; + foreach (var asset in assetsByKeyword) + { + Console.WriteLine($"{counter++}) {asset.Name}"); + if (counter == 6) + { + break; + } + } + } + else + { + Console.WriteLine("No assets found by keyword"); + } + } + + private async Task AuthenticateWithOAuth2Async(bool useClientCredentials) + { + if (useClientCredentials) + { + await _bynderClient.GetOAuthService().GetAccessTokenAsync(); + } + else + { + Browser.Launch(_bynderClient.GetOAuthService().GetAuthorisationUrl("state example")); + Console.WriteLine("Insert the code: "); + var code = Console.ReadLine(); + await _bynderClient.GetOAuthService().GetAccessTokenAsync(code); + } + } + + } +} diff --git a/Bynder/Sample/MediaSample.cs b/Bynder/Sample/MediaSample.cs index ed92cad..146e463 100644 --- a/Bynder/Sample/MediaSample.cs +++ b/Bynder/Sample/MediaSample.cs @@ -32,6 +32,7 @@ private MediaSample(Configuration configuration) { private async Task RunMediaSampleAsync() { + // Get a list of media with limit 10 Console.WriteLine("Listing media with limit of 10: "); var mediaList = await _bynderClient.GetAssetService().GetMediaListAsync(new MediaQuery{Limit=10}); @@ -54,7 +55,14 @@ private async Task RunMediaSampleAsync() Console.WriteLine($"ID: {mediaInfo.Id}"); Console.WriteLine($"Name: {mediaInfo.Name}"); Console.WriteLine($"Brand Id: {mediaInfo.BrandId}"); - Console.WriteLine($"Asset type: {string.Join(',', mediaInfo.PropertyAssetType)}"); + if (mediaInfo.PropertyAssetType == null) + { + Console.WriteLine($"No asset type"); + } + else + { + Console.WriteLine($"Asset type: {string.Join(',', mediaInfo.PropertyAssetType)}"); + } if (mediaInfo.PropertyOptionsDictionary != null) { foreach (var propertyKey in mediaInfo.PropertyOptionsDictionary.Keys) diff --git a/Bynder/Sample/Properties/launchSettings.json b/Bynder/Sample/Properties/launchSettings.json index 6c8a765..3434b7c 100644 --- a/Bynder/Sample/Properties/launchSettings.json +++ b/Bynder/Sample/Properties/launchSettings.json @@ -39,6 +39,10 @@ "Run ModifyMediaSample": { "commandName": "Project", "commandLineArgs": "ModifyMediaSample" + }, + "Run FindMediaSample": { + "commandName": "Project", + "commandLineArgs": "FindMediaSample" } } } \ No newline at end of file From 113ed530a6e3ac83aff580cea670c69ab7ae2562 Mon Sep 17 00:00:00 2001 From: Quirijn Slings Date: Thu, 19 Sep 2024 10:00:31 +0200 Subject: [PATCH 07/10] searching via option id as well --- Bynder/Sample/FindMediaSample.cs | 40 +++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/Bynder/Sample/FindMediaSample.cs b/Bynder/Sample/FindMediaSample.cs index 7cd3da0..bc2ae85 100644 --- a/Bynder/Sample/FindMediaSample.cs +++ b/Bynder/Sample/FindMediaSample.cs @@ -48,8 +48,9 @@ private async Task RunFindMediaSampleAsync() private async Task PerformSearch(IDictionary metaProperties) { Console.WriteLine("You have the following meta properties in your Bynder environment: "); + var mpKeys = metaProperties.Keys.OrderBy(k => k); var counter = 1; - foreach (var metaProperty in metaProperties) + foreach (var metaProperty in metaProperties.OrderBy(mp => mp.Key)) { var extraInfo = metaProperty.Value.Options?.Any() ?? false ? $"[with {metaProperty.Value.Options.Count()} options]" : "[without options]"; Console.WriteLine($"{counter++}) {metaProperty.Key} {extraInfo}"); @@ -60,7 +61,8 @@ private async Task PerformSearch(IDictionary metaPropertie { mpNr = 1; } - var selectedMetaProperty = metaProperties.Skip(mpNr - 1).FirstOrDefault().Value; + var selectedMetaPropertyKey = mpKeys.Skip(mpNr - 1).FirstOrDefault(); + var selectedMetaProperty = metaProperties[selectedMetaPropertyKey]; if (selectedMetaProperty == null) { Console.WriteLine("No meta property found, stopping execution"); @@ -72,7 +74,8 @@ private async Task PerformSearch(IDictionary metaPropertie if (selectedMetaProperty.Options?.Any() ?? false) { counter = 1; - foreach (var option in selectedMetaProperty.Options) + var sortedOptions = selectedMetaProperty.Options.OrderBy(o => o.Label); + foreach (var option in sortedOptions) { Console.WriteLine($"{counter++}) {option.Label}"); } @@ -82,10 +85,10 @@ private async Task PerformSearch(IDictionary metaPropertie { mpNr = 1; } - var selectedOption = selectedMetaProperty.Options.Skip(mpNr - 1).FirstOrDefault(); + var selectedOption = sortedOptions.Skip(mpNr - 1).FirstOrDefault(); searchString = selectedOption.Name; - Console.WriteLine("Searching via the meta property"); + Console.WriteLine($"Searching via the meta property named {selectedMetaProperty.Name} and option named {searchString}"); var assets = await _bynderClient.GetAssetService().GetMediaListAsync(new MediaQuery() { MetaProperties = new Dictionary> @@ -111,8 +114,33 @@ private async Task PerformSearch(IDictionary metaPropertie } else { - Console.WriteLine("No assets found by metaproperty"); + Console.WriteLine("No assets found by metaproperty name / option name"); } + + Console.WriteLine($"Searching via the meta property option ID {selectedOption.Id}"); + assets = await _bynderClient.GetAssetService().GetMediaListAsync(new MediaQuery() + { + PropertyOptionId = [ selectedOption.Id ] + }); + + if (assets?.Any() ?? false) + { + Console.WriteLine($"Found {assets.Count} assets, showing first 5"); + counter = 1; + foreach (var asset in assets) + { + Console.WriteLine($"{counter++}) {asset.Name}"); + if (counter == 6) + { + break; + } + } + } + else + { + Console.WriteLine("No assets found by metaproperty option id"); + } + } else { From db108a5a8489733e5e1616369a2c11fa8b1ba992 Mon Sep 17 00:00:00 2001 From: Quirijn Slings Date: Thu, 19 Sep 2024 11:02:22 +0200 Subject: [PATCH 08/10] Refactored querydecoder because you cannot simply remove the dot for dictionaries, as they are used by multiple query classes --- Bynder/Sample/UploadSample.cs | 6 ++++++ Bynder/Sdk/Query/Asset/MediaQuery.cs | 2 +- Bynder/Sdk/Query/Decoder/ApiField.cs | 7 ++++++- Bynder/Sdk/Query/Decoder/QueryDecoder.cs | 3 ++- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Bynder/Sample/UploadSample.cs b/Bynder/Sample/UploadSample.cs index 21a054a..7e1da81 100644 --- a/Bynder/Sample/UploadSample.cs +++ b/Bynder/Sample/UploadSample.cs @@ -78,6 +78,12 @@ private async Task RunUploadSampleAsync() query.Description = description; } + + query.MetapropertyOptions = new Dictionary>() + { + { "a", [ "b" ]} + }; + FileStream fileStream = null; if (passAsStream) { diff --git a/Bynder/Sdk/Query/Asset/MediaQuery.cs b/Bynder/Sdk/Query/Asset/MediaQuery.cs index 09b0869..0c6d87c 100644 --- a/Bynder/Sdk/Query/Asset/MediaQuery.cs +++ b/Bynder/Sdk/Query/Asset/MediaQuery.cs @@ -84,7 +84,7 @@ public class MediaQuery /// - If the metaproperty is of type text, the values refer to the text itself /// /// - [ApiField("property_", Converter = typeof(MetapropertyOptionsConverter))] + [ApiField("property_", Converter = typeof(MetapropertyOptionsConverter), OmitSeparator = true)] public IDictionary> MetaProperties { get; set; } } diff --git a/Bynder/Sdk/Query/Decoder/ApiField.cs b/Bynder/Sdk/Query/Decoder/ApiField.cs index d9e8ae1..b23424a 100644 --- a/Bynder/Sdk/Query/Decoder/ApiField.cs +++ b/Bynder/Sdk/Query/Decoder/ApiField.cs @@ -1,4 +1,4 @@ -// Copyright (c) Bynder. All rights reserved. +// Copyright (c) Bynder. All rights reserved. // Licensed under the MIT License. See LICENSE file in the project root for full license information. using System; @@ -30,5 +30,10 @@ public ApiField(string name) /// Name of the property in the API documentation. /// public string ApiName { get; private set; } + + /// + /// Indicates whether or not the separator (usually a dot) must be omitted when converting + /// + public bool OmitSeparator { get; set; } } } diff --git a/Bynder/Sdk/Query/Decoder/QueryDecoder.cs b/Bynder/Sdk/Query/Decoder/QueryDecoder.cs index 7a2a022..5bf54cf 100644 --- a/Bynder/Sdk/Query/Decoder/QueryDecoder.cs +++ b/Bynder/Sdk/Query/Decoder/QueryDecoder.cs @@ -68,9 +68,10 @@ private void ConvertProperty(PropertyInfo propertyInfo, object query, IDictionar else if (Activator.CreateInstance(apiField.Converter) is ITypeToDictionaryConverter dictConverter && dictConverter.CanConvert(propertyInfo.PropertyType)) { + var separator = apiField.OmitSeparator ? string.Empty : "."; foreach (var item in dictConverter.Convert(value)) { - AddParam(parameters, $"{apiField.ApiName}{item.Key}", item.Value); + AddParam(parameters, $"{apiField.ApiName}{separator}{item.Key}", item.Value); } } } From 5897c042208cd139624e643c6c370f83d32e0a15 Mon Sep 17 00:00:00 2001 From: Quirijn Slings Date: Fri, 20 Sep 2024 12:58:51 +0200 Subject: [PATCH 09/10] added test method to specify meta names manually --- Bynder/Sample/FindMediaSample.cs | 33 ++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/Bynder/Sample/FindMediaSample.cs b/Bynder/Sample/FindMediaSample.cs index bc2ae85..6a2d5eb 100644 --- a/Bynder/Sample/FindMediaSample.cs +++ b/Bynder/Sample/FindMediaSample.cs @@ -45,8 +45,37 @@ private async Task RunFindMediaSampleAsync() } } + private async Task SearchCustom() + { + Console.WriteLine("Enter metaproperty name: "); + var mpName = Console.ReadLine(); + Console.WriteLine("Enter option name: "); + var optionName = Console.ReadLine(); + Console.WriteLine($"Searching via the meta property named {mpName} and option named {optionName}"); + var assets = await _bynderClient.GetAssetService().GetMediaListAsync(new MediaQuery() + { + MetaProperties = new Dictionary> + { + { + mpName, [ optionName ] + } + } + }); + + Console.WriteLine($"Found {assets.Count()} assets"); + Console.WriteLine("Do you want to search again? (y/N)"); + var again = Console.ReadLine(); + if (again.ToLower().StartsWith("y")) + { + await SearchCustom(); + } + } + private async Task PerformSearch(IDictionary metaProperties) { + await SearchCustom(); + + Console.WriteLine("You have the following meta properties in your Bynder environment: "); var mpKeys = metaProperties.Keys.OrderBy(k => k); var counter = 1; @@ -67,7 +96,7 @@ private async Task PerformSearch(IDictionary metaPropertie { Console.WriteLine("No meta property found, stopping execution"); - return ; + return; } string searchString = null; @@ -120,7 +149,7 @@ private async Task PerformSearch(IDictionary metaPropertie Console.WriteLine($"Searching via the meta property option ID {selectedOption.Id}"); assets = await _bynderClient.GetAssetService().GetMediaListAsync(new MediaQuery() { - PropertyOptionId = [ selectedOption.Id ] + PropertyOptionId = [selectedOption.Id] }); if (assets?.Any() ?? false) From 9eccddb2dc0c882d83278d87e0ebeb046ee0027a Mon Sep 17 00:00:00 2001 From: Quirijn Slings Date: Fri, 20 Sep 2024 18:17:51 +0200 Subject: [PATCH 10/10] improved samples to demonstrate the use of meta properties in MediaQuery and in UploadQuery --- Bynder/Sample/UploadSample.cs | 71 +++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/Bynder/Sample/UploadSample.cs b/Bynder/Sample/UploadSample.cs index 7e1da81..532ccc7 100644 --- a/Bynder/Sample/UploadSample.cs +++ b/Bynder/Sample/UploadSample.cs @@ -78,11 +78,15 @@ private async Task RunUploadSampleAsync() query.Description = description; } - - query.MetapropertyOptions = new Dictionary>() + Console.WriteLine("Next, we're going to select some meta properties and options to add to this asset. Do you want to specify the options as a name (n) or as an id (i)?"); + var optionMode = Console.ReadLine(); + + var metaPropertiesToAdd = await CollectMetaPropertiesAndOptions(optionMode.ToLower().StartsWith("n")); + query.MetapropertyOptions = new Dictionary>(); + foreach (var mp in metaPropertiesToAdd) { - { "a", [ "b" ]} - }; + query.MetapropertyOptions.Add(mp.Key, [mp.Value]); + } FileStream fileStream = null; if (passAsStream) @@ -96,6 +100,65 @@ private async Task RunUploadSampleAsync() } + private async Task>> CollectMetaPropertiesAndOptions(bool useOptionName = false) + { + var metaProperties = await _bynderClient.GetAssetService().GetMetapropertiesAsync(); + Console.WriteLine("You have the following meta properties in your Bynder environment: "); + var mpKeys = metaProperties.Keys.OrderBy(k => k); + var counter = 1; + foreach (var metaProperty in metaProperties.OrderBy(mp => mp.Key)) + { + var extraInfo = metaProperty.Value.Options?.Any() ?? false ? $"[with {metaProperty.Value.Options.Count()} options]" : "[without options]"; + Console.WriteLine($"{counter++}) {metaProperty.Key} {extraInfo}"); + } + Console.WriteLine("Type the number of the meta property to attach to the asset: "); + + var mpNrInput = Console.ReadLine(); + if (!int.TryParse(mpNrInput, out int mpNr)) + { + mpNr = 1; + } + var selectedMetaPropertyKey = mpKeys.Skip(mpNr - 1).FirstOrDefault(); + var selectedMetaProperty = metaProperties[selectedMetaPropertyKey]; + if (selectedMetaProperty == null) + { + Console.WriteLine("No meta property found, stopping execution"); + return []; + } + + if (selectedMetaProperty.Options?.Any() ?? false) + { + counter = 1; + var sortedOptions = selectedMetaProperty.Options.OrderBy(o => o.Label); + foreach (var option in sortedOptions) + { + Console.WriteLine($"{counter++}) {option.Label}"); + } + Console.WriteLine("Type the number of the option to attach to the asset: "); + mpNrInput = Console.ReadLine(); + if (!int.TryParse(mpNrInput, out mpNr)) + { + mpNr = 1; + } + var selectedOption = sortedOptions.Skip(mpNr - 1).FirstOrDefault(); + + var list = new List>(); + list.Add(new KeyValuePair(selectedMetaProperty.Id, useOptionName ? selectedOption.Name : selectedOption.Id)); + Console.WriteLine("Do you want to add another meta property? (y/N)"); + var again = Console.ReadLine(); + if (again.ToLower().StartsWith("y")) + { + list.AddRange(await CollectMetaPropertiesAndOptions()); + } + return list; + } + else + { + Console.WriteLine("The metaproperty you selected does not contain options and cannot be used during upload"); + } + return []; + } + private Dictionary GetCustomParameters() { Console.WriteLine("Do you want to add custom parameters during the upload? (y/n)");