private static async Task TestGettingSlidesByListOfBarcodesAsync(Uri serverBaseUri, string tokenType, string accessToken)
        {
            try
            {
                var slideBarcodes = new[]
                {
                    "2*2U*1*1",
                    "2*2U*2*1",
                    "2*2U*1Y*1",
                    "1>2U>1",
                    "1>2U>2",
                    "1>2U>3"
                };

                var client = new PrimaRestODataClient(serverBaseUri, accessToken);
                var slides = await client.TranslateBarcodesToIdentifiersAsync(slideBarcodes);

                Console.WriteLine("");
                foreach (var slide in slides)
                {
                    Console.WriteLine($"Found Slide: {slide.PrimaryIdentifier + (!string.IsNullOrWhiteSpace(slide.AlternateIdentifier) ? $" ({slide.AlternateIdentifier})" : "")} with barcode '{slide.BarcodeContent}'");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("There was a problem with TranslateBarcodesToIdentifiersAsync");
                Console.WriteLine(ex);
            }
        }
        private static async Task Main(string[] args)
        {
            //read configuration strings from appsettings.json file
            var config = new ConfigurationBuilder().AddJsonFile("appsettings.json", true, true)
                         .Build();

            var apiConfigSection = config.GetSection("ApiConfig");
            var apiConfig        = new ApiConfig();

            apiConfigSection.Bind(apiConfig);

            var serverBaseUri     = new Uri(apiConfig.ServerUrl);
            var authenticationUri = new Uri(apiConfig.AuthenticationUrl);

            //use client_credentials grant type to retrieve a token
            //Expected response: {"access_token":"<token>","expires_in":3600,"token_type":"Bearer"}
            Console.WriteLine($"Using client credentials (Client Id: {apiConfig.Credentials.ClientId}) to retrieve an access token...");
            var clientCredentialsTokenResponse = await GetTokenResponseAsync_ClientCredentialFlow(authenticationUri, apiConfig.Credentials.ClientId, apiConfig.Credentials.ClientSecret);

            Console.WriteLine($"Response:{Environment.NewLine}{clientCredentialsTokenResponse.ToPrettyString()}");
            Console.WriteLine();

            //use password grant type to retrieve a token
            //Expected response: {"access_token":"<token>","expires_in":3600,"token_type":"Bearer"}
            Console.WriteLine($"Using client credentials and resource owner password (Client Id: {apiConfig.Credentials.ClientId}, User: {apiConfig.Credentials.Username}) to retrieve an access token...");
            var resourceOwnerPasswordTokenResponse =
                await GetTokenResponseAsync_ResourceOwnerPasswordFlow(authenticationUri, apiConfig.Credentials.ClientId, apiConfig.Credentials.ClientSecret, apiConfig.Credentials.Username, apiConfig.Credentials.Password);

            Console.WriteLine($"Response:{Environment.NewLine}{resourceOwnerPasswordTokenResponse.ToPrettyString()}");
            Console.WriteLine();

            //use password grant type with offline_access scope to retrieve a token and a refresh token
            //Expected response: {"access_token":"<token>","expires_in":3600,"token_type":"Bearer","refresh_token":"<refresh_token>"}
            Console.WriteLine($"Using client credentials and resource owner password (Client Id: {apiConfig.Credentials.ClientId}, User: {apiConfig.Credentials.Username}) to retrieve an access token AND refresh token...");
            var getRefreshTokenResponse =
                await GetTokenResponseAsync_ResourceOwnerPasswordFlow(authenticationUri, apiConfig.Credentials.ClientId, apiConfig.Credentials.ClientSecret, apiConfig.Credentials.Username, apiConfig.Credentials.Password, true);

            Console.WriteLine($"Response:{Environment.NewLine}{getRefreshTokenResponse.ToPrettyString()}");
            Console.WriteLine();

            //parse previous response to get refresh token for next call
            var refreshToken = GetRefreshTokenFromRefreshTokenResponse(getRefreshTokenResponse);

            if (!string.IsNullOrEmpty(refreshToken))
            {
                //use refresh token grant type to retrieve new access token/refresh token if access token expires
                //Expected response: {"access_token":"<token>","expires_in":3600,"token_type":"Bearer"}
                Console.WriteLine($"Using refresh token ({refreshToken}) to retrieve an access token and refresh token...");

                var refreshTokenResponse = await GetTokenResponseAsync_RefreshTokenFlow(authenticationUri, apiConfig.Credentials.ClientId, apiConfig.Credentials.ClientSecret, refreshToken);

                Console.WriteLine($"Response:{Environment.NewLine}{refreshTokenResponse.ToPrettyString()}");
                Console.WriteLine();
            }

            //extract token type and access token from client credentials access token response
            var(accessToken, tokenType) = GetAccessTokenAndTypeFromTokenResponse(clientCredentialsTokenResponse);

            if (!string.IsNullOrEmpty(tokenType) && !string.IsNullOrEmpty(accessToken))
            {
                //use acquired access token to query for some data about slides

                //find slides by barcode content
                //var barcodeContent = "5*2U*UM9*1";
                var barcodeContent = "1>2U>3";
                var client         = new PrimaRestODataClient(serverBaseUri, accessToken);
                var slide          = await client.FindSlideByBarcodeContentAsync(barcodeContent);

                Console.WriteLine(slide != null ? $"Response:{Environment.NewLine}{slide.PrimaryIdentifier}" : $"Response: Not Found");
                Console.WriteLine();

                //get first slide from the collection
                //var slides = (JArray)JObject.Parse(getSlidesByBarcodeResponse)["value"];
                //var slide = slides[0];

                ////get slide's case id
                //var caseId = slide["caseBaseId"]
                //    .Value<int>();

                ////look up case info using slide's case id
                //var getCaseByIdResponse = await GetCaseByIdAsync(serverBaseUri, caseId, tokenType, accessToken);
                //Console.WriteLine($"Response:{Environment.NewLine}{getCaseByIdResponse.ToPrettyString()}");
                //Console.WriteLine();

                ////check case for study id, may not be present depending on case type
                //var slideCase = JObject.Parse(getCaseByIdResponse);

                //if (slideCase.TryGetValue("studyId", out var studyId))
                //{
                //    var intStudyId = studyId.Value<int?>();
                //    if (intStudyId != null)
                //    {
                //        var studyByIdResponse = await GetStudyByIdAsync(serverBaseUri, intStudyId.Value, tokenType, accessToken);
                //        Console.WriteLine($"Response:{Environment.NewLine}{studyByIdResponse.ToPrettyString()}");
                //        Console.WriteLine();
                //    }
                //}

                //get a page of the stain tests collection, skipping the first 100 items
                var stainsResponse = await GetPagedStainTestsAsync(serverBaseUri, 100, tokenType, accessToken);

                Console.WriteLine($"Response:{Environment.NewLine}{stainsResponse.ToPrettyString()}");
                Console.WriteLine();

                await TestGettingSlidesByListOfBarcodesAsync(serverBaseUri, tokenType, accessToken);
            }
        }