Por motivos de rendimiento, las colecciones de entidades a menudo se dividen en páginas y cada página se devuelve con una dirección URL a la página siguiente. La clase PageIterator simplifica el consumo de colecciones paginadas.
PageIterator controla la enumeración de la página actual y la solicitud de páginas posteriores automáticamente.
Como alternativa, puede usar la @odata.nextLink
propiedad para solicitar manualmente páginas posteriores.
Si envía encabezados de solicitud adicionales en la solicitud inicial, esos encabezados no se incluyen de forma predeterminada en las solicitudes de página posteriores. Si es necesario enviar esos encabezados en solicitudes posteriores, debe establecerlos explícitamente.
Iteración en todos los mensajes
En el ejemplo siguiente se muestra la iteración de todos los mensajes del buzón de un usuario.
Sugerencia
En este ejemplo se establece un tamaño de página pequeño mediante el top
parámetro para fines de demostración. Puede establecer el tamaño de página en 999 para minimizar el número de solicitudes necesarias.
var messages = await graphClient.Me.Messages
.GetAsync(requestConfiguration =>
{
requestConfiguration.QueryParameters.Top = 10;
requestConfiguration.QueryParameters.Select =
["sender", "subject", "body"];
requestConfiguration.Headers.Add(
"Prefer", "outlook.body-content-type=\"text\"");
});
if (messages == null)
{
return;
}
var pageIterator = PageIterator<Message, MessageCollectionResponse>
.CreatePageIterator(
graphClient,
messages,
// Callback executed for each item in
// the collection
(msg) =>
{
Console.WriteLine(msg.Subject);
return true;
},
// Used to configure subsequent page
// requests
(req) =>
{
// Re-add the header to subsequent requests
req.Headers.Add("Prefer", "outlook.body-content-type=\"text\"");
return req;
});
await pageIterator.IterateAsync();
import (
"context"
"fmt"
"log"
"time"
abstractions "github.com/microsoft/kiota-abstractions-go"
graph "github.com/microsoftgraph/msgraph-sdk-go"
graphcore "github.com/microsoftgraph/msgraph-sdk-go-core"
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/users"
)
headers := abstractions.NewRequestHeaders()
headers.Add("Prefer", "outlook.body-content-type=\"text\"")
var pageSize int32 = 10
query := users.ItemMessagesRequestBuilderGetQueryParameters{
Select: []string{"body", "sender", "subject"},
Top: &pageSize,
}
options := users.ItemMessagesRequestBuilderGetRequestConfiguration{
Headers: headers,
QueryParameters: &query,
}
result, err := graphClient.Me().Messages().Get(context.Background(), &options)
if err != nil {
log.Fatalf("Error getting messages: %v\n", err)
}
// Initialize iterator
pageIterator, err := graphcore.NewPageIterator[*models.Message](
result,
graphClient.GetAdapter(),
models.CreateMessageCollectionResponseFromDiscriminatorValue)
if err != nil {
log.Fatalf("Error creating page iterator: %v\n", err)
}
// Any custom headers sent in original request should also be added
// to the iterator
pageIterator.SetHeaders(headers)
// Iterate over all pages
err = pageIterator.Iterate(
context.Background(),
func(message *models.Message) bool {
fmt.Printf("%s\n", *message.GetSubject())
// Return true to continue the iteration
return true
})
if err != nil {
log.Fatalf("Error iterating over messages: %v\n", err)
}
ArrayList<Message> messages = new ArrayList<>();
MessageCollectionResponse messageResponse = graphClient.me().messages().get( requestConfiguration -> {
requestConfiguration.headers.add("Prefer", "outlook.body-content-type=\"text\"");
requestConfiguration.queryParameters.select = new String[] {"sender, subject, body"};
requestConfiguration.queryParameters.top = 10;
});
PageIterator<Message, MessageCollectionResponse> pageIterator =
new PageIterator.Builder<Message, MessageCollectionResponse>()
.client(graphClient)
// Response from the first request
.collectionPage(Objects.requireNonNull(messageResponse))
// Factory to create a new collection response
.collectionPageFactory(MessageCollectionResponse::createFromDiscriminatorValue)
// Used to configure subsequent requests
.requestConfigurator( requestInfo -> {
// Re-add the header and query parameters to subsequent requests
requestInfo.headers.add("Prefer", "outlook.body-content-type=\"text\"");
requestInfo.addQueryParameter("%24select", new String[] {"sender, subject, body"});
requestInfo.addQueryParameter("%24top", 10);
return requestInfo;
})
// Callback executed for each item in the collection
.processPageItemCallback( message -> {
messages.add(message);
return true;
}).build();
pageIterator.iterate();
$query = new MessagesRequestBuilderGetQueryParameters(
top: 10,
select: ['sender', 'subject', 'body']);
$config = new MessagesRequestBuilderGetRequestConfiguration(
queryParameters: $query,
headers: ['Prefer' => 'outlook.body-content-type="text"']);
$messages = $graphClient->me()
->messages()
->get($config)
->wait();
// Microsoft\Graph\Core\Tasks\PageIterator
$pageIterator = new PageIterator($messages, $graphClient->getRequestAdapter());
$callback = function($message): bool {
/** @var Models\Message $message */
print($message->getSubject().PHP_EOL);
// Return true to continue iteration
return true;
};
// Re-add the header to subsequent requests
$pageIterator->setHeaders(['Prefer' => 'outlook.body-content-type="text"']);
$pageIterator->iterate($callback);
const response: PageCollection = await graphClient
.api('/me/messages?$top=10&$select=sender,subject,body')
.header('Prefer', 'outlook.body-content-type="text"')
.get();
// A callback function to be called for every item in the collection.
// This call back should return boolean indicating whether not to
// continue the iteration process.
const callback: PageIteratorCallback = (message: Message) => {
console.log(message.subject);
return true;
};
// A set of request options to be applied to
// all subsequent page requests
const requestOptions: GraphRequestOptions = {
// Re-add the header to subsequent requests
headers: {
Prefer: 'outlook.body-content-type="text"',
},
};
// Creating a new page iterator instance with client a graph client
// instance, page collection response from request and callback
const pageIterator = new PageIterator(
graphClient,
response,
callback,
requestOptions,
);
// This iterates the collection until the nextLink is drained out.
await pageIterator.iterate();
Detener y reanudar la iteración
Algunos escenarios requieren detener el proceso de iteración para realizar otras acciones. Es posible pausar la iteración devolviendo false
de la devolución de llamada de iteración. La iteración se puede reanudar llamando al resume
método en PageIterator.
int count = 0;
int pauseAfter = 25;
var messages = await graphClient.Me.Messages
.GetAsync(requestConfiguration =>
{
requestConfiguration.QueryParameters.Top = 10;
requestConfiguration.QueryParameters.Select =
["sender", "subject"];
});
if (messages == null)
{
return;
}
var pageIterator = PageIterator<Message, MessageCollectionResponse>
.CreatePageIterator(
graphClient,
messages,
(msg) =>
{
Console.WriteLine(msg.Subject);
count++;
// If we've iterated over the limit,
// stop the iteration by returning false
return count < pauseAfter;
});
await pageIterator.IterateAsync();
while (pageIterator.State != PagingState.Complete)
{
Console.WriteLine("Iteration paused for 5 seconds...");
await Task.Delay(5000);
// Reset count
count = 0;
await pageIterator.ResumeAsync();
}
import (
"context"
"fmt"
"log"
"time"
abstractions "github.com/microsoft/kiota-abstractions-go"
graph "github.com/microsoftgraph/msgraph-sdk-go"
graphcore "github.com/microsoftgraph/msgraph-sdk-go-core"
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/users"
)
var pageSize int32 = 10
query := users.ItemMessagesRequestBuilderGetQueryParameters{
Select: []string{"body", "sender", "subject"},
Top: &pageSize,
}
options := users.ItemMessagesRequestBuilderGetRequestConfiguration{
QueryParameters: &query,
}
result, err := graphClient.Me().Messages().Get(context.Background(), &options)
if err != nil {
log.Fatalf("Error getting messages: %v\n", err)
}
// Initialize iterator
pageIterator, err := graphcore.NewPageIterator[*models.Message](
result,
graphClient.GetAdapter(),
models.CreateMessageCollectionResponseFromDiscriminatorValue)
if err != nil {
log.Fatalf("Error creating page iterator: %v\n", err)
}
// Pause iterating after 25
var count, pauseAfter = 0, 25
// Iterate over all pages
err = pageIterator.Iterate(
context.Background(),
func(message *models.Message) bool {
count++
fmt.Printf("%d: %s\n", count, *message.GetSubject())
// Once count = 25, this returns false,
// Which pauses the iteration
return count < pauseAfter
})
if err != nil {
log.Fatalf("Error iterating over messages: %v\n", err)
}
// Pause 5 seconds
fmt.Printf("Iterated first %d messages, pausing for 5 seconds...\n", pauseAfter)
time.Sleep(5 * time.Second)
fmt.Printf("Resuming iteration...\n")
// Resume iteration
err = pageIterator.Iterate(
context.Background(),
func(message *models.Message) bool {
count++
fmt.Printf("%d: %s\n", count, *message.GetSubject())
// Return true to continue the iteration
return true
})
if err != nil {
log.Fatalf("Error iterating over messages: %v\n", err)
}
int iterations = 1;
ArrayList<Message> messages = new ArrayList<>();
int pauseAfter = iterations*25;
MessageCollectionResponse messageResponse = graphClient.me().messages().get( requestConfiguration -> {
requestConfiguration.queryParameters.top = 10;
requestConfiguration.queryParameters.select = new String[] {"sender, subject"};
});
PageIterator<Message, MessageCollectionResponse> pageIterator =
new PageIterator.Builder<Message, MessageCollectionResponse>()
.client(graphClient)
.collectionPage(Objects.requireNonNull(messageResponse))
.collectionPageFactory(MessageCollectionResponse::createFromDiscriminatorValue)
.requestConfigurator( requestInfo -> {
requestInfo.addQueryParameter("%24select", new String[] {"sender, subject"});
requestInfo.addQueryParameter("%24top", 10);
return requestInfo;
})
.processPageItemCallback( message -> {
messages.add(message);
// Pause paging by returning false after 25 messages
return messages.size() < pauseAfter;
}).build();
pageIterator.iterate();
// Resume paging
while (pageIterator.getPageIteratorState() != PageIterator.PageIteratorState.COMPLETE) {
iterations+=1;
pageIterator.resume();
}
$count = 0;
$messages = $graphClient->me()
->messages()
->get()
->wait();
// Microsoft\Graph\Core\Tasks\PageIterator
$pageIterator = new PageIterator($messages, $graphClient->getRequestAdapter());
$callback = function($message) use (&$count): bool {
/** @var Models\Message $message */
$count++;
print($count.'. '.$message->getSubject().PHP_EOL);
// Return true to continue iteration
// Return false once first 5 have been processed
return $count < 5;
};
$pageIterator->iterate($callback);
print('Pausing iteration after first 5'.PHP_EOL);
sleep(5);
// Process next 5
$count = 0;
$pageIterator->iterate($callback);
let count = 0;
const pauseAfter = 25;
const response: PageCollection = await graphClient
.api('/me/messages?$top=10&$select=sender,subject,body')
.get();
const callback: PageIteratorCallback = (message: Message) => {
console.log(message.subject);
count++;
// If we've iterated over the limit,
// stop the iteration by returning false
return count < pauseAfter;
};
const pageIterator = new PageIterator(graphClient, response, callback);
await pageIterator.iterate();
while (!pageIterator.isComplete()) {
console.log('Iteration paused for 5 seconds...');
await new Promise((resolve) => setTimeout(resolve, 5000));
// Reset count
count = 0;
await pageIterator.resume();
}
Solicitud manual de páginas posteriores
Como alternativa al uso de la clase PageIterator , puede comprobar manualmente la respuesta de una @odata.nextLink
propiedad y solicitar la página siguiente.
var messages = await graphClient.Me.Messages
.GetAsync(requestConfiguration =>
{
requestConfiguration.QueryParameters.Top = 10;
});
while (messages?.Value != null)
{
foreach (var message in messages.Value)
{
Console.WriteLine(message.Subject);
}
// If OdataNextLink has a value, there is another page
if (!string.IsNullOrEmpty(messages.OdataNextLink))
{
// Pass the OdataNextLink to the WithUrl method
// to request the next page
messages = await graphClient.Me.Messages
.WithUrl(messages.OdataNextLink)
.GetAsync();
}
else
{
// No more results, exit loop
break;
}
}
import (
"context"
"fmt"
"log"
"time"
abstractions "github.com/microsoft/kiota-abstractions-go"
graph "github.com/microsoftgraph/msgraph-sdk-go"
graphcore "github.com/microsoftgraph/msgraph-sdk-go-core"
"github.com/microsoftgraph/msgraph-sdk-go/models"
"github.com/microsoftgraph/msgraph-sdk-go/users"
)
var pageSize int32 = 10
query := users.ItemMessagesRequestBuilderGetQueryParameters{
Top: &pageSize,
}
options := users.ItemMessagesRequestBuilderGetRequestConfiguration{
QueryParameters: &query,
}
result, err := graphClient.Me().Messages().Get(context.Background(), &options)
if err != nil {
log.Fatalf("Error getting messages: %v\n", err)
}
for {
for _, message := range result.GetValue() {
fmt.Printf("%s\n", *message.GetSubject())
}
nextPageUrl := result.GetOdataNextLink()
if nextPageUrl != nil {
result, err = graphClient.Me().Messages().
WithUrl(*nextPageUrl).
Get(context.Background(), nil)
if err != nil {
log.Fatalf("Error getting messages: %v\n", err)
}
} else {
break
}
}
MessageCollectionResponse messagesPage = graphClient.me().messages().get( requestConfiguration -> {
requestConfiguration.headers.add("Prefer", "outlook.body-content-type=\"text\"");
requestConfiguration.queryParameters.select = new String[] {"sender, subject, body"};
requestConfiguration.queryParameters.top = 10;
});
while (messagesPage != null) {
final List<Message> messages = messagesPage.getValue();
for (Message message : messages) {
System.out.println(message.getSubject());
}
// Get the next page
final String odataNextLink = messagesPage.getOdataNextLink();
if (odataNextLink == null || odataNextLink.isEmpty()) {
break;
} else {
messagesPage = graphClient.me().messages().withUrl(odataNextLink).get();
}
}
/** @var MessageCollectionResponse $messages */
$messages = $graphClient->me()
->messages()
->get()
->wait();
while (null !== $messages->getValue())
{
foreach($messages->getValue() as $message) {
/** @var Models\Message $message */
print($message->getSubject().PHP_EOL);
}
if (null !== $messages->getOdataNextLink()) {
$messages = $graphClient->me()
->messages()
->withUrl($messages->getOdataNextLink())
->get()
->wait();
}
else {
break;
}
}
let response: PageCollection = await graphClient
.api('/me/messages?$top=10')
.get();
while (response.value.length > 0) {
for (const message of response.value as Message[]) {
console.log(message.subject);
}
if (response['@odata.nextLink']) {
response = await graphClient.api(response['@odata.nextLink']).get();
} else {
break;
}
}
Control de errores
Evitar errores de DirectoryPageTokenNotFoundException
Al paginar a través de grandes conjuntos de datos, es posible que encuentre el DirectoryPageTokenNotFoundException
error, lo que impide que la aplicación cliente recupere correctamente las páginas posteriores. Este error se produce cuando la aplicación cliente usa un token de una operación de reintento para solicitar la siguiente página de resultados.
Para evitar este error, no use tokens de operaciones de reintento para solicitudes de página posteriores, ya que no se garantiza que estos tokens sean válidos para futuras solicitudes. En su lugar, conserve el token de la última respuesta correcta y úselo para la siguiente solicitud de página. Por lo tanto, el @odata.nextLink
valor usado para el reintento debe usarse para la solicitud de página posterior.
Escenario de ejemplo
- Recupere la página 1 y reciba un token "Token1".
- Use "Token1" para solicitar la página 2.
- Si encuentra un error de red, vuelva a intentar la solicitud.
- Durante el reintento, recibirá un nuevo token "RetryToken".
- No use "RetryToken" para solicitar la página 3, ya que podría provocar el
DirectoryPageTokenNotFoundException
error.
- En su lugar, use "Token1" (el token de la última respuesta correcta sin reintento) para solicitar la página 3.