As orquestrações de Funções Duráveis são implementadas no código e podem usar os recursos internos de tratamento de erros da linguagem de programação. Não há novos conceitos que você precise aprender para adicionar a compensação e o tratamento de erro às suas orquestrações. No entanto, há alguns comportamentos a que você deve estar atento.
Observação
A versão 4 do modelo de programação Node.js para o Azure Functions está disponível para o público geral. O novo modelo v4 foi projetado para oferecer uma experiência mais flexível e intuitiva para desenvolvedores JavaScript e TypeScript. Saiba mais sobre as diferenças entre v3 e v4 no guia de migração.
Nos trechos de código a seguir, o JavaScript (PM4) denota o modelo de programação V4, a nova experiência.
Erros em funções de atividade e suborquestrações
No Durable Functions, exceções sem tratamento geradas dentro de funções de atividade ou suborquestrações são enviadas de volta para a função orquestradora usando tipos de exceção padronizados.
Por exemplo, considere a seguinte função de orquestrador que executa uma transferência de fundo entre duas contas:
Em processo C# do Durable Functions, exceções não tratadas são lançadas como FunctionFailedException.
A mensagem de exceção normalmente identifica quais funções de atividade ou sub-orquestrações causaram a falha. Para acessar informações de erro mais detalhadas, inspecione a InnerException propriedade.
[FunctionName("TransferFunds")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var transferDetails = context.GetInput<TransferOperation>();
await context.CallActivityAsync("DebitAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
try
{
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.DestinationAccount,
Amount = transferDetails.Amount
});
}
catch (FunctionFailedException)
{
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
}
}
Observação
O exemplo de C# anterior é para o Durable Functions 2.x. No Durable Functions 1.x, use DurableOrchestrationContext em vez de IDurableOrchestrationContext. Para obter mais informações sobre as diferenças entre versões, confira o artigo Versões do Durable Functions.
Em Funções Duráveis C# Isoladas, exceções sem tratamento são exibidas como TaskFailedException.
A mensagem de exceção normalmente identifica quais funções de atividade ou sub-orquestrações causaram a falha. Para acessar informações de erro mais detalhadas, inspecione a propriedade FailureDetails .
[FunctionName("TransferFunds")]
public static async Task Run(
[OrchestrationTrigger] TaskOrchestrationContext context, TransferOperation transferDetails)
{
await context.CallActivityAsync("DebitAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
try
{
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.DestinationAccount,
Amount = transferDetails.Amount
});
}
catch (TaskFailedException)
{
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
await context.CallActivityAsync("CreditAccount",
new
{
Account = transferDetails.SourceAccount,
Amount = transferDetails.Amount
});
}
}
Observação
- A mensagem de exceção normalmente identifica quais funções de atividade ou sub-orquestrações causaram a falha. Para acessar informações de erro mais detalhadas, inspecione a
FailureDetails propriedade.
- Por padrão,
FailureDetails inclui o tipo de erro, a mensagem de erro, o rastreamento da pilha e quaisquer exceções internas aninhadas (cada uma representada como um objeto FailureDetails recursivo). Se você quiser incluir propriedades de exceção adicionais na saída de falha, consulte Incluir Propriedades de Exceção Personalizadas para FailureDetails (.NET Isolado).
const df = require("durable-functions");
module.exports = df.orchestrator(function* (context) {
const transferDetails = context.df.getInput();
yield context.df.callActivity("DebitAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
try {
yield context.df.callActivity("CreditAccount", {
account: transferDetails.destinationAccount,
amount: transferDetails.amount,
});
} catch (error) {
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
yield context.df.callActivity("CreditAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
}
})
const df = require("durable-functions");
df.app.orchestration("transferFunds", function* (context) {
const transferDetails = context.df.getInput();
yield context.df.callActivity("debitAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
try {
yield context.df.callActivity("creditAccount", {
account: transferDetails.destinationAccount,
amount: transferDetails.amount,
});
} catch (error) {
// Refund the source account.
// Another try/catch could be used here based on the needs of the application.
yield context.df.callActivity("creditAccount", {
account: transferDetails.sourceAccount,
amount: transferDetails.amount,
});
}
});
import azure.functions as func
import azure.durable_functions as df
def orchestrator_function(context: df.DurableOrchestrationContext):
transfer_details = context.get_input()
yield context.call_activity('DebitAccount', {
'account': transfer_details['sourceAccount'],
'amount' : transfer_details['amount']
})
try:
yield context.call_activity('CreditAccount', {
'account': transfer_details['destinationAccount'],
'amount': transfer_details['amount'],
})
except:
yield context.call_activity('CreditAccount', {
'account': transfer_details['sourceAccount'],
'amount': transfer_details['amount']
})
main = df.Orchestrator.create(orchestrator_function)
Por padrão, os cmdlets no PowerShell não geram exceções que podem ser capturadas usando blocos try/catch. Você tem duas opções para alterar esse comportamento:
- Use o sinalizador
-ErrorAction Stop ao invocar cmdlets, como Invoke-DurableActivity.
- Defina a variável de preferência
$ErrorActionPreference como "Stop" na função de orquestrador antes de invocar os cmdlets.
param($Context)
$ErrorActionPreference = "Stop"
$transferDetails = $Context.Input
Invoke-DurableActivity -FunctionName 'DebitAccount' -Input @{ account = transferDetails.sourceAccount; amount = transferDetails.amount }
try {
Invoke-DurableActivity -FunctionName 'CreditAccount' -Input @{ account = transferDetails.destinationAccount; amount = transferDetails.amount }
} catch {
Invoke-DurableActivity -FunctionName 'CreditAccount' -Input @{ account = transferDetails.sourceAccount; amount = transferDetails.amount }
}
Para obter mais informações sobre o tratamento de erro no PowerShell, consulte a documentação do PowerShell Try-Catch-Finally.
@FunctionName("TransferFunds")
public void transferFunds(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
TransferOperation transfer = ctx.getInput(TransferOperation.class);
ctx.callActivity(
"DebitAccount",
new OperationArgs(transfer.sourceAccount, transfer.amount)).await();
try {
ctx.callActivity(
"CreditAccount",
new OperationArgs(transfer.destinationAccount, transfer.amount)).await();
} catch (TaskFailedException ex) {
// Refund the source account on failure
ctx.callActivity(
"CreditAccount",
new OperationArgs(transfer.sourceAccount, transfer.amount)).await();
}
}
Se a primeira chamada de função CreditAccount falhar, a função de orquestrador compensará creditando os fundos de volta à conta de origem.
Erros em funções de entidade
O comportamento de tratamento de exceção para funções de entidade difere com base no modelo de hospedagem do Durable Functions:
No Durable Functions usando o C# em processo, os tipos de exceção originais gerados por funções de entidade são retornados diretamente ao orquestrador.
[FunctionName("Function1")]
public static async Task<string> RunOrchestrator(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
try
{
var entityId = new EntityId(nameof(Counter), "myCounter");
await context.CallEntityAsync(entityId, "Add", 1);
}
catch (Exception ex)
{
// The exception type will be InvalidOperationException with the message "this is an entity exception".
}
return string.Empty;
}
[FunctionName("Counter")]
public static void Counter([EntityTrigger] IDurableEntityContext ctx)
{
switch (ctx.OperationName.ToLowerInvariant())
{
case "add":
throw new InvalidOperationException("this is an entity exception");
case "get":
ctx.Return(ctx.GetState<int>());
break;
}
}
Em Funções Duráveis Isoladas em C#, as exceções são exibidas para o orquestrador como um EntityOperationFailedException. Para acessar os detalhes da exceção original, inspecione a sua propriedade FailureDetails.
[Function(nameof(MyOrchestrator))]
public static async Task<List<string>> MyOrchestrator(
[Microsoft.Azure.Functions.Worker.OrchestrationTrigger] TaskOrchestrationContext context)
{
var entityId = new Microsoft.DurableTask.Entities.EntityInstanceId(nameof(Counter), "myCounter");
try
{
await context.Entities.CallEntityAsync(entityId, "Add", 1);
}
catch (EntityOperationFailedException ex)
{
// Add your error handling
}
return new List<string>();
}
df.app.orchestration("counterOrchestration", function* (context) {
const entityId = new df.EntityId(counterEntityName, "myCounter");
try {
const currentValue = yield context.df.callEntity(entityId, "get");
if (currentValue < 10) {
yield context.df.callEntity(entityId, "add", 1);
}
} catch (err) {
context.log(`Entity call failed: ${err.message ?? err}`);
}
});
df.app.orchestration("counterOrchestration", function* (context) {
const entityId = new df.EntityId(counterEntityName, "myCounter");
try {
const currentValue = yield context.df.callEntity(entityId, "get");
if (currentValue < 10) {
yield context.df.callEntity(entityId, "add", 1);
}
} catch (err) {
context.log(`Entity call failed: ${err.message ?? err}`);
}
});
@myApp.orchestration_trigger(context_name="context")
def run_orchestrator(context):
try:
entityId = df.EntityId("Counter", "myCounter")
yield context.call_entity(entityId, "get")
return "finished"
except Exception as e:
# Add your error handling
No momento, não há suporte para funções de entidade no PowerShell.
No momento, não há suporte para funções de entidade no Java.
Repetição automática em caso de falha
Quando chama funções de atividade ou funções de suborquestração, você pode especificar uma política de repetição automática. O exemplo a seguir tenta chamar uma função até três vezes e espera cinco segundos entre cada repetição:
[FunctionName("TimerOrchestratorWithRetry")]
public static async Task Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
var retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(5),
maxNumberOfAttempts: 3);
await context.CallActivityWithRetryAsync("FlakyFunction", retryOptions, null);
// ...
}
Observação
O exemplo de C# anterior é para o Durable Functions 2.x. No Durable Functions 1.x, use DurableOrchestrationContext em vez de IDurableOrchestrationContext. Para obter mais informações sobre as diferenças entre versões, confira o artigo Versões do Durable Functions.
[FunctionName("TimerOrchestratorWithRetry")]
public static async Task Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
var options = TaskOptions.FromRetryPolicy(new RetryPolicy(
maxNumberOfAttempts: 3,
firstRetryInterval: TimeSpan.FromSeconds(5)));
await context.CallActivityAsync("FlakyFunction", options: options);
// ...
}
const df = require("durable-functions");
module.exports = df.orchestrator(function*(context) {
const firstRetryIntervalInMilliseconds = 5000;
const maxNumberOfAttempts = 3;
const retryOptions =
new df.RetryOptions(firstRetryIntervalInMilliseconds, maxNumberOfAttempts);
yield context.df.callActivityWithRetry("FlakyFunction", retryOptions);
// ...
});
const df = require("durable-functions");
df.app.orchestration("callActivityWithRetry", function* (context) {
const firstRetryIntervalInMilliseconds = 5000;
const maxNumberOfAttempts = 3;
const retryOptions = new df.RetryOptions(firstRetryIntervalInMilliseconds, maxNumberOfAttempts);
yield context.df.callActivityWithRetry("flakyFunction", retryOptions);
// ...
});
import azure.functions as func
import azure.durable_functions as df
def orchestrator_function(context: df.DurableOrchestrationContext):
first_retry_interval_in_milliseconds = 5000
max_number_of_attempts = 3
retry_options = df.RetryOptions(first_retry_interval_in_milliseconds, max_number_of_attempts)
yield context.call_activity_with_retry('FlakyFunction', retry_options)
main = df.Orchestrator.create(orchestrator_function)
param($Context)
$retryOptions = New-DurableRetryOptions `
-FirstRetryInterval (New-TimeSpan -Seconds 5) `
-MaxNumberOfAttempts 3
Invoke-DurableActivity -FunctionName 'FlakyFunction' -RetryOptions $retryOptions
@FunctionName("TimerOrchestratorWithRetry")
public void timerOrchestratorWithRetry(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
final int maxAttempts = 3;
final Duration firstRetryInterval = Duration.ofSeconds(5);
RetryPolicy policy = new RetryPolicy(maxAttempts, firstRetryInterval);
TaskOptions options = new TaskOptions(policy);
ctx.callActivity("FlakeyFunction", options).await();
// ...
}
A chamada de função de atividade no exemplo anterior recebe um parâmetro para configurar uma política de nova tentativa automática. Há várias opções para personalizar a política de repetição automática:
-
Núm. máximo de tentativas: o número máximo de tentativas de repetição. Se definido como 1, não haverá nenhuma repetição.
-
Primeiro intervalo de repetição: o tempo de espera antes de fazer a primeira tentativa.
-
Coeficiente de retirada: o coeficiente usado para determinar a taxa de aumento de retirada. O valor padrão é 1.
-
Intervalo máx. de repetição: o tempo máximo de espera entre tentativas de repetição.
-
Tempo limite de repetição: o tempo máximo a ser dedicado às novas tentativas. O comportamento padrão é repetir indefinidamente.
Manipuladores de repetição personalizados
Ao usar o .NET ou o Java, você também tem a opção de implementar manipuladores de repetição no código. Isso é útil quando políticas declarativas de nova tentativa não são expressivas o suficiente. Para linguagens que não dão suporte a manipuladores de repetição personalizados, você ainda tem a opção de implementar políticas de repetição usando loops, tratamento de exceção e temporizadores para injetar atrasos entre novas tentativas.
RetryOptions retryOptions = new RetryOptions(
firstRetryInterval: TimeSpan.FromSeconds(5),
maxNumberOfAttempts: int.MaxValue)
{
Handle = exception =>
{
// True to handle and try again, false to not handle and throw.
if (exception is TaskFailedException failure)
{
// Exceptions from TaskActivities are always this type. Inspect the
// inner Exception to get more details.
}
return false;
};
}
await ctx.CallActivityWithRetryAsync("FlakeyActivity", retryOptions, null);
TaskOptions retryOptions = TaskOptions.FromRetryHandler(retryContext =>
{
// Don't retry anything that derives from ApplicationException
if (retryContext.LastFailure.IsCausedBy<ApplicationException>())
{
return false;
}
// Quit after N attempts
return retryContext.LastAttemptNumber < 3;
});
try
{
await ctx.CallActivityAsync("FlakeyActivity", options: retryOptions);
}
catch (TaskFailedException)
{
// Case when the retry handler returns false...
}
No momento, o JavaScript não dá suporte a manipuladores de repetição personalizados. No entanto, você ainda tem a opção de implementar a lógica de repetição diretamente na função de orquestrador usando loops, tratamento de exceção e temporizadores para injetar atrasos entre novas tentativas.
No momento, o JavaScript não dá suporte a manipuladores de repetição personalizados. No entanto, você ainda tem a opção de implementar a lógica de repetição diretamente na função de orquestrador usando loops, tratamento de exceção e temporizadores para injetar atrasos entre novas tentativas.
No momento, o Python não dá suporte a manipuladores de repetição personalizados. No entanto, você ainda tem a opção de implementar a lógica de repetição diretamente na função de orquestrador usando loops, tratamento de exceção e temporizadores para injetar atrasos entre novas tentativas.
No momento, o PowerShell não dá suporte a manipuladores de repetição personalizados. No entanto, você ainda tem a opção de implementar a lógica de repetição diretamente na função de orquestrador usando loops, tratamento de exceção e temporizadores para injetar atrasos entre novas tentativas.
RetryHandler retryHandler = retryCtx -> {
// Don't retry anything that derives from RuntimeException
if (retryCtx.getLastFailure().isCausedBy(RuntimeException.class)) {
return false;
}
// Quit after N attempts
return retryCtx.getLastAttemptNumber() < 3;
};
TaskOptions options = new TaskOptions(retryHandler);
try {
ctx.callActivity("FlakeyActivity", options).await();
} catch (TaskFailedException ex) {
// Case when the retry handler returns false...
}
Tempos limite de função
Talvez você queira abandonar uma chamada de função dentro de uma função de orquestrador se ela estiver demorando muito para ser concluída. No momento, o modo adequado de fazer isso é criar um temporizador durável com “qualquer” seletor de tarefa, conforme mostrado no exemplo a seguir:
[FunctionName("TimerOrchestrator")]
public static async Task<bool> Run([OrchestrationTrigger] IDurableOrchestrationContext context)
{
TimeSpan timeout = TimeSpan.FromSeconds(30);
DateTime deadline = context.CurrentUtcDateTime.Add(timeout);
using (var cts = new CancellationTokenSource())
{
Task activityTask = context.CallActivityAsync("FlakyFunction");
Task timeoutTask = context.CreateTimer(deadline, cts.Token);
Task winner = await Task.WhenAny(activityTask, timeoutTask);
if (winner == activityTask)
{
// success case
cts.Cancel();
return true;
}
else
{
// timeout case
return false;
}
}
}
Observação
O exemplo de C# anterior é para o Durable Functions 2.x. No Durable Functions 1.x, use DurableOrchestrationContext em vez de IDurableOrchestrationContext. Para obter mais informações sobre as diferenças entre versões, confira o artigo Versões do Durable Functions.
[Function("TimerOrchestrator")]
public static async Task<bool> Run([OrchestrationTrigger] TaskOrchestrationContext context)
{
TimeSpan timeout = TimeSpan.FromSeconds(30);
DateTime deadline = context.CurrentUtcDateTime.Add(timeout);
using (var cts = new CancellationTokenSource())
{
Task activityTask = context.CallActivityAsync("FlakyFunction");
Task timeoutTask = context.CreateTimer(deadline, cts.Token);
Task winner = await Task.WhenAny(activityTask, timeoutTask);
if (winner == activityTask)
{
// success case
cts.Cancel();
return true;
}
else
{
// timeout case
return false;
}
}
}
const df = require("durable-functions");
const moment = require("moment");
module.exports = df.orchestrator(function*(context) {
const deadline = moment.utc(context.df.currentUtcDateTime).add(30, "s");
const activityTask = context.df.callActivity("FlakyFunction");
const timeoutTask = context.df.createTimer(deadline.toDate());
const winner = yield context.df.Task.any([activityTask, timeoutTask]);
if (winner === activityTask) {
// success case
timeoutTask.cancel();
return true;
} else {
// timeout case
return false;
}
});
const df = require("durable-functions");
const { DateTime } = require("luxon");
df.app.orchestration("timerOrchestrator", function* (context) {
const deadline = DateTime.fromJSDate(context.df.currentUtcDateTime).plus({ seconds: 30 });
const activityTask = context.df.callActivity("flakyFunction");
const timeoutTask = context.df.createTimer(deadline.toJSDate());
const winner = yield context.df.Task.any([activityTask, timeoutTask]);
if (winner === activityTask) {
// success case
timeoutTask.cancel();
return true;
} else {
// timeout case
return false;
}
});
import azure.functions as func
import azure.durable_functions as df
from datetime import datetime, timedelta
def orchestrator_function(context: df.DurableOrchestrationContext):
deadline = context.current_utc_datetime + timedelta(seconds = 30)
activity_task = context.call_activity('FlakyFunction')
timeout_task = context.create_timer(deadline)
winner = yield context.task_any(activity_task, timeout_task)
if winner == activity_task:
timeout_task.cancel()
return True
else:
return False
main = df.Orchestrator.create(orchestrator_function)
param($Context)
$expiryTime = New-TimeSpan -Seconds 30
$activityTask = Invoke-DurableActivity -FunctionName 'FlakyFunction'-NoWait
$timerTask = Start-DurableTimer -Duration $expiryTime -NoWait
$winner = Wait-DurableTask -Task @($activityTask, $timerTask) -NoWait
if ($winner -eq $activityTask) {
Stop-DurableTimerTask -Task $timerTask
return $True
}
else {
return $False
}
@FunctionName("TimerOrchestrator")
public boolean timerOrchestrator(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
Task<Void> activityTask = ctx.callActivity("SlowFunction");
Task<Void> timeoutTask = ctx.createTimer(Duration.ofMinutes(30));
Task<?> winner = ctx.anyOf(activityTask, timeoutTask).await();
if (winner == activityTask) {
// success case
return true;
} else {
// timeout case
return false;
}
}
Observação
Esse mecanismo não realmente encerra a execução de uma função de atividade em andamento. Em vez disso, ele simplesmente permite que a função de orquestrador ignore o resultado e prossiga. Para mais informações, confira a documentação dos Medidores de tempo.
Exceções sem tratamento
Se uma função de orquestrador falhar com uma exceção sem tratamento, os detalhes da exceção serão registrados e a instância será concluída com um status Failed.
Incluir propriedades de exceção personalizadas para FailureDetails (.NET Isolated)
Ao executar fluxos de trabalho do `Durable Task` no modelo isolado do .NET, as falhas de tarefa são automaticamente serializadas em um objeto FailureDetails. Por padrão, esse objeto inclui campos padrão, como:
- ErrorType — o nome do tipo de exceção
- Mensagem – a mensagem de exceção
- StackTrace — o rastreamento de pilha serializado
- InnerFailure – um objeto FailureDetails incorporado para exceções internas recursivas
Começando com Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0, você pode estender esse comportamento implementando um IExceptionPropertiesProvider (definido no pacote Microsoft.DurableTask.Worker a partir da v1.16.1). Esse provedor define quais tipos de exceção e quais de suas propriedades devem ser incluídas no dicionário FailureDetails.Properties.
Observação
- Esse recurso está disponível somente no .NET Isolado . O suporte para Java será adicionado em uma versão futura.
- Verifique se você está usando Microsoft.Azure.Functions.Worker.Extensions.DurableTask v1.9.0 ou posterior.
- Verifique se você está usando Microsoft.DurableTask.Worker v1.16.1 ou posterior.
Implementar um provedor de propriedades de exceção
Implemente um IExceptionPropertiesProvider personalizado para extrair e retornar propriedades selecionadas para as exceções relevantes. O dicionário retornado será serializado no campo Propriedades de FailureDetails quando um tipo de exceção correspondente for gerado.
using Microsoft.DurableTask.Worker;
public class CustomExceptionPropertiesProvider : IExceptionPropertiesProvider
{
public IDictionary<string, object?>? GetExceptionProperties(Exception exception)
{
return exception switch
{
ArgumentOutOfRangeException e => new Dictionary<string, object?>
{
["ParamName"] = e.ParamName,
["ActualValue"] = e.ActualValue
},
InvalidOperationException e => new Dictionary<string, object?>
{
["CustomHint"] = "Invalid operation occurred",
["TimestampUtc"] = DateTime.UtcNow
},
_ => null // Other exception types not handled
};
}
}
Registrar o provedor
Registre seu IExceptionPropertiesProvider personalizado no host de trabalho isolado do .NET, normalmente em Program.cs:
using Microsoft.DurableTask.Worker;
using Microsoft.Extensions.DependencyInjection;
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults(builder =>
{
// Register custom exception properties provider
builder.Services.AddSingleton<IExceptionPropertiesProvider, CustomExceptionPropertiesProvider>();
})
.Build();
host.Run();
Depois de registrado, qualquer exceção que corresponda a um dos tipos manipulados incluirá automaticamente as propriedades configuradas em seu FailureDetails.
Exemplo de Saída de FailureDetails
Quando ocorre uma exceção que corresponde à configuração do provedor, a orquestração recebe uma estrutura FailureDetails serializada como esta:
{
"errorType": "TaskFailedException",
"message": "Activity failed with an exception.",
"stackTrace": "...",
"innerFailure": {
"errorType": "ArgumentOutOfRangeException",
"message": "Specified argument was out of range.",
"properties": {
"ParamName": "count",
"ActualValue": 42
}
}
}
Próximas etapas