Partilhar via


ASP.NET Web Forms Resiliência da Conexão e Interceção de Comandos

por Erik Reitan

Neste tutorial, irá modificar a aplicação de exemplo Wingtip Toys para suportar resiliência de ligação e interceção de comandos. Ao ativar a resiliência da ligação, a aplicação de exemplo Wingtip Toys tentará automaticamente chamadas de dados quando ocorrerem erros transitórios típicos de um ambiente cloud. Além disso, ao implementar a interceção de comandos, a aplicação de exemplo Wingtip Toys irá capturar todas as consultas SQL enviadas para a base de dados para as registar ou alterar.

Observação

Este tutorial Web Forms baseou-se no seguinte tutorial MVC de Tom Dykstra:
Resiliência da Ligação e Interceção de Comandos com o Entity Framework numa Aplicação MVC ASP.NET

O que você vai aprender:

  • Como proporcionar resiliência na ligação.
  • Como implementar a interceção por comando.

Pré-requisitos

Antes de começar, certifique-se de que tem o seguinte software instalado no seu computador:

Resiliência da conexão

Quando considera implementar uma aplicação no Windows Azure, uma opção a considerar é implementar a base de dados para a base de dados SQL do WindowsAzure, um serviço de base de dados na cloud. Erros de ligação transitória são tipicamente mais frequentes quando se liga a um serviço de base de dados na nuvem do que quando o seu servidor web e o seu servidor de base de dados estão diretamente ligados no mesmo centro de dados. Mesmo que um servidor web na cloud e um serviço de base de dados na cloud estejam alojados no mesmo centro de dados, existem mais ligações de rede entre eles que podem ter problemas, como os balanceadores de carga.

Além disso, um serviço na cloud é normalmente partilhado por outros utilizadores, o que significa que a sua capacidade de resposta pode ser afetada por eles. E o teu acesso à base de dados pode ser sujeito a restrições. Limitação significa que o serviço de base de dados lança exceções quando tenta aceder a ele com mais frequência do que o permitido no seu Acordo de Nível de Serviço (SLA).

Muitos ou a maioria dos problemas de ligação que ocorrem quando acedes a um serviço cloud são transitórios, ou seja, resolvem-se sozinhos num curto espaço de tempo. Por isso, quando tenta uma operação numa base de dados e obtém um tipo de erro que normalmente é transitório, pode tentar novamente após uma curta espera, e a operação pode ser bem-sucedida. Pode proporcionar uma experiência muito melhor aos seus utilizadores se lidar com erros transitórios ao tentar automaticamente novamente, tornando a maioria invisível para o cliente. A funcionalidade de resiliência da conexão no Entity Framework 6 automatiza esse processo de reexecutar consultas SQL que falharam.

A funcionalidade de resiliência de ligação deve ser configurada adequadamente para um determinado serviço de base de dados:

  1. Tem de saber quais as exceções que provavelmente serão transitórias. Queres tentar novamente erros causados por uma perda temporária de conectividade de rede, não erros causados por bugs de programa, por exemplo.
  2. Deve aguardar um tempo apropriado entre as tentativas de repetir uma operação falhada. Pode esperar mais tempo entre tentativas para um processo em lote do que para uma página online onde um utilizador está à espera de uma resposta.
  3. Tem de tentar novamente o número apropriado de vezes antes de desistir. Poderá querer tentar mais vezes num processo em lote do que numa aplicação online.

Pode configurar estas definições manualmente para qualquer ambiente de base de dados suportado por um fornecedor de Entity Framework.

Tudo o que tens de fazer para ativar a resiliência da ligação é criar uma classe no teu assembly que derive da DbConfiguration classe, e nessa classe definir a estratégia de execução da base de dados SQL, que no Entity Framework é outro termo para política de retentativas.

Implementação da resiliência da ligação

  1. Descarregue e abra a aplicação de exemplo Web Forms WingtipToys no Visual Studio.

  2. Na pasta Logic da aplicação WingtipToys , adicione um ficheiro de classe chamado WingtipToysConfiguration.cs.

  3. Substitua o código existente pelo seguinte:

    using System.Data.Entity;
    using System.Data.Entity.SqlServer;
     
    namespace WingtipToys.Logic
    {
        public class WingtipToysConfiguration : DbConfiguration
        {
            public WingtipToysConfiguration()
            {
              SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy());
            }
        }
    }
    

O Entity Framework executa automaticamente o código que encontra numa classe que deriva de DbConfiguration. Pode usar a classe DbConfiguration para realizar tarefas de configuração em código que, de outra forma, faria no ficheiro Web.config. Para mais informações, consulte Configuração com Base em Código do EntityFramework.

  1. Na pasta Logic , abre o ficheiro AddProducts.cs .

  2. Adicione uma using declaração para System.Data.Entity.Infrastructure como mostrado destacado a amarelo:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using WingtipToys.Models;
    using System.Data.Entity.Infrastructure;
    
  3. Adicione um catch bloco ao AddProduct método para que o RetryLimitExceededException fique registado como destacado a amarelo:

    public bool AddProduct(string ProductName, string ProductDesc, string ProductPrice, string ProductCategory, string ProductImagePath)
    {
        var myProduct = new Product();
        myProduct.ProductName = ProductName;
        myProduct.Description = ProductDesc;
        myProduct.UnitPrice = Convert.ToDouble(ProductPrice);
        myProduct.ImagePath = ProductImagePath;
        myProduct.CategoryID = Convert.ToInt32(ProductCategory);
    
        using (ProductContext _db = new ProductContext())
        {
            // Add product to DB.
            _db.Products.Add(myProduct);
            try
            {
                _db.SaveChanges();
            }
            catch (RetryLimitExceededException ex)
            {
                // Log the RetryLimitExceededException.
                WingtipToys.Logic.ExceptionUtility.LogException(ex, "Error: RetryLimitExceededException -> RemoveProductButton_Click in AdminPage.aspx.cs");
            }
        }
        // Success.
        return true;
    }
    

Ao adicionar a RetryLimitExceededException exceção, pode fornecer um melhor registo ou mostrar uma mensagem de erro ao utilizador, onde este pode optar por tentar o processo novamente. Ao apanhar a RetryLimitExceededException exceção, os únicos erros provavelmente transitórios já terão sido tentados e falhados várias vezes. A exceção real que for devolvida será encapsulada na exceção RetryLimitExceededException. Além disso, também adicionaste um bloco de captura geral. Para mais informações sobre a RetryLimitExceededException exceção, consulte Resiliência de Conexão do Entity Framework / Tentativa de Repetição.

Interceção por comando

Agora que ligaste uma política de tentativas adicionais, como testas para verificar se está a funcionar como esperado? Não é assim tão fácil forçar um erro transitório a acontecer, especialmente quando está a correr localmente, e seria especialmente difícil integrar erros transitórios reais num teste unitário automatizado. Para testar a funcionalidade de resiliência da ligação, é necessário uma forma de intercetar consultas que o Entity Framework envia para o SQL Server e substituir a resposta do SQL Server por um tipo de exceção que normalmente seja transitório.

Também pode usar a interceção de consultas para implementar uma boa prática para aplicações na cloud: registar a latência e o sucesso ou falha de todas as chamadas para serviços externos, como serviços de base de dados.

Nesta secção do tutorial, vais usar a funcionalidade de interceção do Entity Framework tanto para registar como para simular erros transitórios.

Criar uma interface de registo e uma classe

Uma boa prática para o registo é fazê-lo usando um interface em vez de codificar diretamente chamadas para System.Diagnostics.Trace ou uma classe de registo. Isso facilita mudar o teu mecanismo de registo mais tarde, caso alguma vez precises de o fazer. Nesta secção, vais criar a interface de registo e uma classe para a implementar.

Com base no procedimento acima, descarregou e abriu a aplicação de exemplo WingtipToys no Visual Studio.

  1. Cria uma pasta no projeto WingtipToys e chama-lhe Logging.

  2. Na pasta Loging , crie um ficheiro de classe chamado ILogger.cs e substitua o código predefinido pelo seguinte código:

    using System;
     
    namespace WingtipToys.Logging
    {
        public interface ILogger
        {
            void Information(string message);
            void Information(string fmt, params object[] vars);
            void Information(Exception exception, string fmt, params object[] vars);
    
            void Warning(string message);
            void Warning(string fmt, params object[] vars);
            void Warning(Exception exception, string fmt, params object[] vars);
    
            void Error(string message);
            void Error(string fmt, params object[] vars);
            void Error(Exception exception, string fmt, params object[] vars);
    
            void TraceApi(string componentName, string method, TimeSpan timespan);
            void TraceApi(string componentName, string method, TimeSpan timespan, string properties);
            void TraceApi(string componentName, string method, TimeSpan timespan, string fmt, params object[] vars);
    
        }
    }
    

    A interface fornece três níveis de rastreio para indicar a importância relativa dos registos, e um concebido para fornecer informação de latência para chamadas de serviço externo, como consultas à base de dados. Os métodos de registo têm sobrecargas que permitem passar uma exceção. Isto serve para que a informação de exceções, incluindo o stack trace e as exceções internas, seja registada de forma fiável pela classe que implementa a interface, em vez de depender do que é feito em cada chamada de método de registo ao longo da aplicação.

    Os TraceApi métodos permitem-lhe acompanhar a latência de cada chamada para um serviço externo, como a Base de Dados SQL.

  3. Na pasta Loging , crie um ficheiro de classe chamado Logger.cs e substitua o código predefinido pelo seguinte código:

    using System;
    using System.Diagnostics;
    using System.Text;
     
    namespace WingtipToys.Logging
    {
      public class Logger : ILogger
      {
     
        public void Information(string message)
        {
          Trace.TraceInformation(message);
        }
     
        public void Information(string fmt, params object[] vars)
        {
          Trace.TraceInformation(fmt, vars);
        }
     
        public void Information(Exception exception, string fmt, params object[] vars)
        {
          Trace.TraceInformation(FormatExceptionMessage(exception, fmt, vars));
        }
     
        public void Warning(string message)
        {
          Trace.TraceWarning(message);
        }
     
        public void Warning(string fmt, params object[] vars)
        {
          Trace.TraceWarning(fmt, vars);
        }
     
        public void Warning(Exception exception, string fmt, params object[] vars)
        {
          Trace.TraceWarning(FormatExceptionMessage(exception, fmt, vars));
        }
     
        public void Error(string message)
        {
          Trace.TraceError(message);
        }
     
        public void Error(string fmt, params object[] vars)
        {
          Trace.TraceError(fmt, vars);
        }
     
        public void Error(Exception exception, string fmt, params object[] vars)
        {
          Trace.TraceError(FormatExceptionMessage(exception, fmt, vars));
        }
     
        public void TraceApi(string componentName, string method, TimeSpan timespan)
        {
          TraceApi(componentName, method, timespan, "");
        }
     
        public void TraceApi(string componentName, string method, TimeSpan timespan, string fmt, params object[] vars)
        {
          TraceApi(componentName, method, timespan, string.Format(fmt, vars));
        }
        public void TraceApi(string componentName, string method, TimeSpan timespan, string properties)
        {
          string message = String.Concat("Component:", componentName, ";Method:", method, ";Timespan:", timespan.ToString(), ";Properties:", properties);
          Trace.TraceInformation(message);
        }
     
        private static string FormatExceptionMessage(Exception exception, string fmt, object[] vars)
        {
          var sb = new StringBuilder();
          sb.Append(string.Format(fmt, vars));
          sb.Append(" Exception: ");
          sb.Append(exception.ToString());
          return sb.ToString();
        }
      }
    }
    

A implementação utiliza System.Diagnostics para fazer o rastreio. Esta é uma funcionalidade incorporada no .NET que facilita a geração e utilização de informação de rastreio. Existem muitos "ouvintes" que podes usar com System.Diagnostics rastreamento, para escrever logs em ficheiros, por exemplo, ou para os gravar em armazenamento de blobs no Windows Azure. Consulte algumas das opções e links para outros recursos para mais informações, em Resolução de Problemas dos Sites Web do Windows Azure no Visual Studio. Neste tutorial, só vais ver os registos na janela de Saída do Visual Studio.

Numa aplicação de produção, pode querer considerar usar frameworks de rastreio diferentes de System.Diagnostics, e a interface ILogger facilita bastante a transição para outro mecanismo de rastreio, se optar por isso.

Criar classes de interceptor

De seguida, vais criar as classes que o Entity Framework irá invocar sempre que for enviar uma consulta à base de dados, uma para simular erros transitórios e outra para fazer registos. Estas classes de interceptores devem derivar da DbCommandInterceptor classe. Nelas, escreve-se sobreposições de métodos que são automaticamente chamadas quando a consulta está prestes a ser executada. Nestes métodos, pode examinar ou registar a consulta que está a ser enviada para a base de dados, e pode alterar a consulta antes de ser enviada para a base de dados ou devolver algo ao Entity Framework sem sequer passar a consulta para a base de dados.

  1. Para criar a classe interceptor que irá registar todas as consultas SQL antes de serem enviadas para a base de dados, crie um ficheiro de classe chamado InterceptorLogging.cs na pasta Logic e substitua o código predefinido pelo seguinte código:

    using System;
    using System.Data.Common;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure.Interception;
    using System.Data.Entity.SqlServer;
    using System.Data.SqlClient;
    using System.Diagnostics;
    using System.Reflection;
    using System.Linq;
    using WingtipToys.Logging;
    
    namespace WingtipToys.Logic
    {
      public class InterceptorLogging : DbCommandInterceptor
      {
        private ILogger _logger = new Logger();
        private readonly Stopwatch _stopwatch = new Stopwatch();
    
        public override void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
          base.ScalarExecuting(command, interceptionContext);
          _stopwatch.Restart();
        }
    
        public override void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
          _stopwatch.Stop();
          if (interceptionContext.Exception != null)
          {
            _logger.Error(interceptionContext.Exception, "Error executing command: {0}", command.CommandText);
          }
          else
          {
            _logger.TraceApi("SQL Database", "Interceptor.ScalarExecuted", _stopwatch.Elapsed, "Command: {0}: ", command.CommandText);
          }
          base.ScalarExecuted(command, interceptionContext);
        }
    
        public override void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
          base.NonQueryExecuting(command, interceptionContext);
          _stopwatch.Restart();
        }
    
        public override void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
          _stopwatch.Stop();
          if (interceptionContext.Exception != null)
          {
            _logger.Error(interceptionContext.Exception, "Error executing command: {0}", command.CommandText);
          }
          else
          {
            _logger.TraceApi("SQL Database", "Interceptor.NonQueryExecuted", _stopwatch.Elapsed, "Command: {0}: ", command.CommandText);
          }
          base.NonQueryExecuted(command, interceptionContext);
        }
    
        public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
          base.ReaderExecuting(command, interceptionContext);
          _stopwatch.Restart();
        }
        public override void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
          _stopwatch.Stop();
          if (interceptionContext.Exception != null)
          {
            _logger.Error(interceptionContext.Exception, "Error executing command: {0}", command.CommandText);
          }
          else
          {
            _logger.TraceApi("SQL Database", "Interceptor.ReaderExecuted", _stopwatch.Elapsed, "Command: {0}: ", command.CommandText);
          }
          base.ReaderExecuted(command, interceptionContext);
        }
      }
    }
    

    Para consultas ou comandos bem-sucedidos, este código escreve um registo de informação com informação de latência. Para exceções, cria-se um registo de erros.

  2. Para criar a classe interceptor que gera erros transitórios fictícios ao introduzir "Throw" na caixa de texto Nome na página chamada AdminPage.aspx, crie um ficheiro de classe chamado InterceptorTransientErrors.cs na pasta Logic e substitua o código padrão pelo seguinte código:

    using System;
    using System.Data.Common;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure.Interception;
    using System.Data.Entity.SqlServer;
    using System.Data.SqlClient;
    using System.Diagnostics;
    using System.Reflection;
    using System.Linq;
    using WingtipToys.Logging;
     
    namespace WingtipToys.Logic
    {
      public class InterceptorTransientErrors : DbCommandInterceptor
      {
        private int _counter = 0;
        private ILogger _logger = new Logger();
     
        public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
          bool throwTransientErrors = false;
          if (command.Parameters.Count > 0 && command.Parameters[0].Value.ToString() == "Throw")
          {
            throwTransientErrors = true;
            command.Parameters[0].Value = "TransientErrorExample";
            command.Parameters[1].Value = "TransientErrorExample";
          }
     
          if (throwTransientErrors && _counter < 4)
          {
            _logger.Information("Returning transient error for command: {0}", command.CommandText);
            _counter++;
            interceptionContext.Exception = CreateDummySqlException();
          }
        }
     
        private SqlException CreateDummySqlException()
        {
          // The instance of SQL Server you attempted to connect to does not support encryption
          var sqlErrorNumber = 20;
     
          var sqlErrorCtor = typeof(SqlError).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).Where(c => c.GetParameters().Count() == 7).Single();
          var sqlError = sqlErrorCtor.Invoke(new object[] { sqlErrorNumber, (byte)0, (byte)0, "", "", "", 1 });
     
          var errorCollection = Activator.CreateInstance(typeof(SqlErrorCollection), true);
          var addMethod = typeof(SqlErrorCollection).GetMethod("Add", BindingFlags.Instance | BindingFlags.NonPublic);
          addMethod.Invoke(errorCollection, new[] { sqlError });
     
          var sqlExceptionCtor = typeof(SqlException).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic).Where(c => c.GetParameters().Count() == 4).Single();
          var sqlException = (SqlException)sqlExceptionCtor.Invoke(new object[] { "Dummy", errorCollection, null, Guid.NewGuid() });
     
          return sqlException;
        }
      }
    }
    

    Este código apenas sobrepõe o método ReaderExecuting, que é chamado para consultas que podem devolver várias linhas de resultado. Se quiser verificar a resiliência da ligação para outros tipos de consultas, também poderá sobrepor os métodos NonQueryExecuting e ScalarExecuting, como faz o interceptor de logging.

    Mais tarde, fará login como "Admin" e selecionará o link Admin na barra de navegação superior. Depois, na página AdminPage.aspx, adiciona um produto chamado "Throw". O código cria uma exceção fictícia na base de dados SQL para o erro número 20, um tipo conhecido por ser tipicamente transitório. Outros números de erro atualmente reconhecidos como transitórios são 64, 233, 10053, 10054, 10060, 10928, 10929, 40197, 40501 e 40613, mas estes estão sujeitos a alterações em novas versões da Base de Dados SQL. O produto será renomeado para "TransientErrorExample", que pode consultar no código do ficheiro InterceptorTransientErrors.cs.

    O código devolve a exceção ao Entity Framework em vez de executar a consulta e devolver os resultados. A exceção transitória é devolvida quatro vezes e, de seguida, o código volta ao procedimento normal de passar a consulta para a base de dados.

    Como tudo está registado, vais conseguir ver que o Entity Framework tenta executar a consulta quatro vezes antes de finalmente ter sucesso, e a única diferença na aplicação é que demora mais tempo a renderizar uma página com os resultados da consulta.

    O número de vezes que o Entity Framework irá tentar novamente é configurável; o código especifica quatro vezes porque esse é o valor padrão para a política de execução da base de dados SQL. Se mudares a política de execução, também mudarias o código aqui que especifica quantas vezes são gerados erros transitórios. Também podes alterar o código para gerar mais exceções, de modo a que o Entity Framework lance a RetryLimitExceededException exceção.

  3. No Global.asax, adicione o seguinte usando instruções:

    using System.Data.Entity.Infrastructure.Interception;
    
  4. Depois, adicione as linhas destacadas ao Application_Start método:

    void Application_Start(object sender, EventArgs e)
    {
        // Code that runs on application startup
        RouteConfig.RegisterRoutes(RouteTable.Routes);
        BundleConfig.RegisterBundles(BundleTable.Bundles);
    
        // Initialize the product database.
        Database.SetInitializer(new ProductDatabaseInitializer());
    
        // Create administrator role and user.
        RoleActions roleActions = new RoleActions();
        roleActions.createAdmin();
    
        // Add Routes.
        RegisterRoutes(RouteTable.Routes);
    
        // Logging.
        DbInterception.Add(new InterceptorTransientErrors());
        DbInterception.Add(new InterceptorLogging());
      
    }
    

Estas linhas de código são o que faz com que o seu código interceptor seja executado quando o Entity Framework envia consultas para a base de dados. Repare que, como criou classes de interceptores separadas para simulação e registo de erros transitórios, pode ativar e desativar de forma independente.

Podes adicionar interceptores usando o DbInterception.Add método em qualquer parte do teu código; não tem de estar no Application_Start método. Outra opção, se não adicionaste interceptores no Application_Start método, seria atualizar ou adicionar a classe chamada WingtipToysConfiguration.cs e colocar o código acima no final do construtor da WingtipToysConfiguration classe.

Onde quer que coloque este código, tenha cuidado para não executar DbInterception.Add para o mesmo interceptor mais do que uma vez, caso contrário terá instâncias adicionais de interceptor. Por exemplo, se adicionares o interceptor de registos duas vezes, vais ver dois registos para cada consulta SQL.

Os interceptores são executados na ordem de registo (a ordem em que o DbInterception.Add método é chamado). A ordem pode importar dependendo do que estás a fazer no interceptor. Por exemplo, um interceptor pode alterar o comando SQL que recebe na CommandText propriedade. Se mudar o comando SQL, o próximo interceptor receberá o comando SQL alterado, não o comando SQL original.

Escreveste o código de simulação de erros transitórios de uma forma que te permite causar erros transitórios ao introduzir um valor diferente na interface. Como alternativa, poderia escrever o código interceptor para gerar sempre a sequência de exceções transitórias sem verificar um valor de parâmetro específico. Poderia então adicionar o interceptor apenas quando quiser gerar erros transitórios. Se fizeres isto, no entanto, não adiciones o interceptor até que a inicialização da base de dados esteja concluída. Por outras palavras, faça pelo menos uma operação na base de dados, como uma consulta a um dos seus conjuntos de entidades, antes de começar a gerar erros transitórios. O Entity Framework executa várias consultas durante a inicialização da base de dados, e estas não são executadas numa transação, pelo que erros durante a inicialização podem causar um estado inconsistente no contexto.

Registo de testes e resiliência da ligação

  1. No Visual Studio, pressiona F5 para executar a aplicação em modo de depuração e depois inicia sessão como "Admin" usando "Pa$$word" como palavra-passe.

  2. Selecione Administrador na barra de navegação no topo.

  3. Introduza um novo produto chamado "Throw" com descrição, preço e ficheiro de imagem apropriados.

  4. Pressione o botão Adicionar Produto.
    Vai reparar que o browser aparenta estar não respondendo durante alguns segundos, enquanto o Entity Framework tenta repetir a consulta várias vezes. A primeira tentativa acontece muito rapidamente, depois a espera aumenta antes de cada tentativa adicional. Este processo de esperar mais tempo antes de cada tentativa de nova execução chama-se recuo exponencial.

  5. Espere até a página deixar de tentar carregar.

  6. Pare o projeto e olhe para a janela Output do Visual Studio para observar o resultado do rastreamento. Pode encontrar a janela de Saída selecionando Debug ->Windows ->Output. Pode ter de passar por vários outros registos escritos pelo seu registrador.

    Repara que consegues ver as consultas SQL enviadas para a base de dados. Vês algumas consultas e comandos iniciais que o Entity Framework faz para começar, verificando a versão da base de dados e a tabela de histórico de migração.
    Janela de saída
    Note que não pode repetir este teste a menos que pare a aplicação e a reinicie. Se quiser poder testar a resiliência da ligação várias vezes numa única execução da aplicação, pode escrever código para reiniciar o contador de erros em InterceptorTransientErrors .

  7. Para ver a diferença que a estratégia de execução (política de retentativa) faz, comente a SetExecutionStrategy linha no ficheiro WingtipToysConfiguration.cs na pasta Logic , execute novamente a página de Admin em modo de depuração e adicione novamente o produto chamado "Throw".

    Desta vez, o depurador para imediatamente na primeira exceção gerada quando tenta executar a consulta pela primeira vez.
    Depuração - Ver Detalhes

  8. Remova o comentário da linha SetExecutionStrategy no ficheiro WingtipToysConfiguration.cs.

Resumo

Neste tutorial viste como modificar uma aplicação de exemplo de Web Forms para suportar resiliência de ligações e interceção de comandos.

Próximas Etapas

Depois de rever a resiliência da conexão e a interceção de comandos no ASP.NET Web Forms, consulte o tópico do ASP.NET Web Forms Métodos Assíncronos em ASP.NET 4.5. O tema vai ensinar-lhe o básico para construir uma aplicação assíncrona ASP.NET Web Forms usando o Visual Studio.