Search Unity

  1. We are migrating the Unity Forums to Unity Discussions. On July 12, the Unity Forums will become read-only. On July 15, Unity Discussions will become read-only until July 18, when the new design and the migrated forum contents will go live. Read our full announcement for more information and let us know if you have any questions.

Question Issue with retrieving more than 20 data from Unity Game Services using pagination

Discussion in 'Unity Gaming Services General Discussion' started by unity_vYgBb07dPcScEA, Mar 11, 2024.

  1. unity_vYgBb07dPcScEA

    unity_vYgBb07dPcScEA

    Joined:
    Jul 19, 2022
    Posts:
    1
    Hello everyone,

    I'm encountering an issue when trying to retrieve more than 20 data from Unity Game Services (UGS) using pagination. I'm developing an application where I need to access a large amount of data stored in UGS through Unity3D's Cloud Save feature. However, despite trying to implement pagination as specified in the UGS API documentation, I always end up getting only the first 20 data.

    I've tried various ways of implementing pagination, such as checking if there are more pages available using the "nextPage" key in the API response, but it seems like this check doesn't work correctly. I always end up getting only the first 20 data and cannot access the rest of the stored data.

    Here's my current code:

    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Net.Http;
    5. using System.Net.Http.Headers;
    6. using Newtonsoft.Json;
    7. using System.Linq;
    8. using System.Threading.Tasks;
    9.  
    10. public class Program
    11. {
    12.     public static async Task Main(string[] args)
    13.     {
    14.         // Clase para autenticación de Bearer
    15.         class BearerAuth
    16.         {
    17.             private readonly string _token;
    18.  
    19.             public BearerAuth(string token)
    20.             {
    21.                 _token = token;
    22.             }
    23.  
    24.             public void AddTokenToRequest(HttpRequestMessage request)
    25.             {
    26.                 request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", _token);
    27.             }
    28.         }
    29.  
    30.         // Identificadores de proyecto y entorno de UGS
    31.         var projectid = "********-****-****-****-************";
    32.         var environmentid = "********-****-****-****-************";
    33.  
    34.         // URL para generar token
    35.         var obtain_token_url = $"https://services.api.unity.com/auth/v1/token-exchange?projectId={projectid}&environmentId={environmentid}";
    36.  
    37.         // Datos de permiso generados en UGS
    38.         var serviceid = "********-****-****-****-************";
    39.         var servicepass = "************";
    40.  
    41.         // Realizar solicitud para obtener token
    42.         var client = new HttpClient();
    43.         var tokenRequestBody = new Dictionary<string, string>
    44.         {
    45.             { "grant_type", "client_credentials" },
    46.             { "client_id", serviceid },
    47.             { "client_secret", servicepass }
    48.         };
    49.         var tokenRequestContent = new FormUrlEncodedContent(tokenRequestBody);
    50.         var tokenResponse = await client.PostAsync(obtain_token_url, tokenRequestContent);
    51.         tokenResponse.EnsureSuccessStatusCode();
    52.         var tokenContent = await tokenResponse.Content.ReadAsStringAsync();
    53.         var tokenData = JsonConvert.DeserializeObject<dynamic>(tokenContent);
    54.         var myToken = tokenData.accessToken;
    55.  
    56.         // ID del jugador de UGS
    57.         var playerid = "********-****-****-****-************";
    58.  
    59.         // URL de la API para obtener los datos del jugador
    60.         var obtain_player_data_base = $"https://cloud-save.services.api.unity.com/v1/data/projects/{projectid}/players/{playerid}/items";
    61.  
    62.         // Lista para almacenar todos los datos del jugador
    63.         var all_player_data = new List<dynamic>();
    64.  
    65.         // Variable para el número de página
    66.         var page = 1;
    67.  
    68.         // Realizar solicitudes para obtener todos los datos de todas las páginas
    69.         while (true)
    70.         {
    71.             // Realizar solicitud GET a la API
    72.             var request = new HttpRequestMessage(HttpMethod.Get, $"{obtain_player_data_base}?page={page}");
    73.             var bearerAuth = new BearerAuth((string)myToken);
    74.             bearerAuth.AddTokenToRequest(request);
    75.             var response = await client.SendAsync(request);
    76.             response.EnsureSuccessStatusCode();
    77.             var responseData = await response.Content.ReadAsStringAsync();
    78.             var data = JsonConvert.DeserializeObject<dynamic>(responseData);
    79.  
    80.             // Obtener los resultados y agregarlos a la lista
    81.             all_player_data.AddRange(data.results);
    82.  
    83.             // Verificar si hay más páginas
    84.             if (data.nextPage == null)
    85.             {
    86.                 break;
    87.             }
    88.  
    89.             // Incrementar el número de página para la próxima solicitud
    90.             page++;
    91.         }
    92.  
    93.         // Eliminar columnas no deseadas
    94.         var deleteColumns = new List<string> { "writeLock", "modified.date", "created.date" };
    95.         foreach (var column in deleteColumns)
    96.         {
    97.             all_player_data.RemoveAll(item => item.ContainsKey(column));
    98.         }
    99.  
    100.         // Imprimir los datos
    101.         foreach (var item in all_player_data)
    102.         {
    103.             Console.WriteLine(item);
    104.         }
    105.     }
    106. }
     
  2. GabKBelmonte

    GabKBelmonte

    Unity Technologies

    Joined:
    Dec 14, 2021
    Posts:
    162
    Hello,

    The first thing I need ask is, why are you building your own web-request?

    You can use the CloudSave SDK: https://docs.unity.com/ugs/en-us/manual/cloud-save/manual/tutorials/unity-sdk-sample

    You can get it from the package manager easily:
    upload_2024-3-13_15-23-42.png

    FWIW, this is what the SDK does internally:

    Code (CSharp):
    1. async Task<Dictionary<string, Item>> LoadWithErrorHandlingAsync(IAccessClassOptions options, ISet<string> keys = null)
    2. {
    3.     return await m_ErrorHandler.RunWithErrorHandling(async() =>
    4.     {
    5.         var result = new Dictionary<string, Item>();
    6.         Response<GetItemsResponse> response;
    7.         string lastAddedKey = null;
    8.         do
    9.         {
    10.             response = await m_PlayerDataApiClient.LoadAsync(keys, lastAddedKey, options.AccessClass, options.PlayerId);
    11.             var items = response.Result.Results;
    12.             if (items.Count > 0)
    13.             {
    14.                 foreach (var item in items)
    15.                 {
    16.                     result[item.Key] = new Item(item);
    17.                 }
    18.  
    19.                 lastAddedKey = items[items.Count - 1].Key;
    20.             }
    21.         }
    22.         while (!string.IsNullOrEmpty(response.Result.Links.Next));
    23.  
    24.         return result;
    25.     });
    26. }

    However, If I'm reading between the lines, you have a custom application that will be using CloudSave or some other dedicated server?

    In that case, I will refer to the admin and client specs:
    Admin: https://services.docs.unity.com/cloud-save-admin/v1/#tag/Data/operation/getItems
    Client: https://services.docs.unity.com/cloud-save/v1/#tag/Data/operation/getItems

    Both of their pagination operations, as well as the sample code from the SDK above, use "after" not "pages", so Im guessing that's why.

    Alternatively, if you want, I describe here: https://forum.unity.com/threads/ser...naging-player-economies.1545188/#post-9644168 the way to generate an API client from the specification, which will help you bootstrap a lot better.

    However, more details on your use-case will go a long way in my capacity to help you.

    Cheers!

    Gab