Поделиться через


Устойчивость подключений и перехват команд в веб-формах ASP.NET

Эрик Рейтан

В этом руководстве вы измените пример приложения Wingtip Toys для поддержки устойчивости подключения и перехвата команд. Включив устойчивость подключения, пример приложения Wingtip Toys автоматически повторяет вызовы данных при возникновении временных ошибок в облачной среде. Кроме того, путем реализации перехвата команд пример приложения Wingtip Toys перехватит все запросы SQL, отправленные в базу данных, чтобы регистрировать или изменять их.

Примечание.

Это руководство по веб-формы было основано на следующем руководстве MVC Tom Dykstra:
Устойчивость подключения и перехват команд с помощью Entity Framework в приложении MVC ASP.NET

Из этого руководства вы узнаете, как выполнять такие задачи:

  • Как обеспечить устойчивость подключения.
  • Как реализовать перехват команд.

Необходимые компоненты

Прежде чем начать, убедитесь, что на компьютере установлено следующее программное обеспечение:

Устойчивость подключения

При развертывании приложения в Windows Azure можно рассмотреть возможность развертывания базы данных в Windows База данных SQL Azure облачной службы баз данных. Временные ошибки подключения обычно чаще возникают при подключении к облачной службе базы данных, чем при подключении веб-сервера и сервера базы данных напрямую к одному центру обработки данных. Даже если облачный веб-сервер и облачная служба баз данных размещаются в одном центре обработки данных, между ними могут возникнуть проблемы, такие как подсистемы балансировки нагрузки.

Кроме того, облачная служба обычно предоставляется другим пользователям, что означает, что ее скорость реагирования может повлиять на них. Доступ к базе данных может быть подвержен регулированию. Регулирование означает, что служба базы данных создает исключения при попытке доступа к нему чаще, чем разрешено в соглашении об уровне обслуживания (SLA).

Многие или большинство проблем с подключением, возникающие при доступе к облачной службе, являются временными, то есть они устраняют себя в течение короткого периода времени. Поэтому при попытке операции базы данных и получении типа ошибки, которая обычно является временной, можно повторить операцию после короткого ожидания, и операция может быть успешной. Вы можете обеспечить гораздо лучший интерфейс для пользователей, если вы обрабатываете временные ошибки, автоматически пытаясь повторить попытку, делая большинство из них невидимыми для клиента. Функция устойчивости подключений в Entity Framework 6 автоматизирует процесс повторных повторных запросов SQL.

Для конкретной службы базы данных необходимо настроить функцию устойчивости подключения:

  1. Он должен знать, какие исключения, скорее всего, будут временными. Вы хотите повторить ошибки, вызванные временной потерей в сетевом подключении, а не ошибками, вызванными ошибками программы, например.
  2. Он должен ждать соответствующее время между повторными попытками неудачной операции. Вы можете ждать больше времени между повторными попытками пакетного процесса, чем для веб-страницы в Интернете, где пользователь ожидает ответа.
  3. Он должен повторить соответствующее количество раз, прежде чем он откажается. Вам может потребоваться повторить больше раз в пакетном процессе, который вы будете использовать в онлайн-приложении.

Эти параметры можно настроить вручную для любой базы данных, поддержка среды поставщиком Entity Framework.

Все, что необходимо сделать, чтобы обеспечить устойчивость подключений, — создать класс в сборке, которая является производным от DbConfiguration класса, и в этом классе задается стратегия выполнения База данных SQL, которая в Entity Framework является еще одним термином для политики повторных попыток.

Реализация устойчивости подключения

  1. Скачайте и откройте пример приложения WingtipToys веб-формы в Visual Studio.

  2. В папке логики приложения WingtipToys добавьте файл класса с именем WingtipToysConfiguration.cs.

  3. Замените существующий код следующим кодом:

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

Entity Framework автоматически запускает код, который он находит в классе, наследуемом от DbConfiguration. Класс можно использовать для выполнения задач конфигурации в коде DbConfiguration , который в противном случае будет выполняться в файле web.config . Дополнительные сведения см. в разделе "Конфигурация на основе кода EntityFramework".

  1. В папке логики откройте файл AddProducts.cs.

  2. using Добавьте оператор, как System.Data.Entity.Infrastructure показано в желтом цвете:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using WingtipToys.Models;
    using System.Data.Entity.Infrastructure;
    
  3. catch Добавьте блок в AddProduct метод, чтобы RetryLimitExceededException регистрировался как выделенный желтым цветом:

    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;
    }
    

RetryLimitExceededException Добавив исключение, вы можете предоставить более эффективное ведение журнала или отобразить пользователю сообщение об ошибке, где он может повторить попытку процесса. Перехватив RetryLimitExceededException исключение, единственные ошибки, которые, вероятно, будут временными, уже были проверены и неудачны несколько раз. Фактическое возвращаемое исключение будет упаковано в RetryLimitExceededException исключение. Кроме того, вы также добавили общий блок catch. Дополнительные сведения об исключении RetryLimitExceededException см. в разделе Entity Framework Connection Resiliency /Retry Logic.

Перехват команд

Теперь, когда вы включили политику повторных попыток, как проверить, работает ли она должным образом? Это не так просто, чтобы принудительно произойти временные ошибки, особенно при локальном запуске, и было бы особенно трудно интегрировать фактические временные ошибки в автоматизированный модульный тест. Чтобы проверить функцию устойчивости подключений, вам потребуется способ перехвата запросов, которые Entity Framework отправляет в SQL Server и заменить ответ SQL Server типом исключения, который обычно является временным.

Вы также можете использовать перехват запросов, чтобы реализовать рекомендации по облачным приложениям: регистрировать задержку и успешность или сбой всех вызовов внешних служб, таких как службы баз данных.

В этом разделе руководства вы будете использовать функцию перехвата Entity Framework как для ведения журнала, так и для имитации временных ошибок.

Создание интерфейса ведения журнала и класса

Рекомендуется выполнить ведение журнала с помощью interface вызовов System.Diagnostics.Trace жесткого программирования или класса ведения журнала. Это упрощает изменение механизма ведения журнала позже, если это нужно сделать. Поэтому в этом разделе вы создадите интерфейс ведения журнала и класс для его реализации.

На основе приведенной выше процедуры вы скачали и открыли пример приложения WingtipToys в Visual Studio.

  1. Создайте папку в проекте WingtipToys и присвойте ей имя ведения журнала.

  2. В папке ведения журнала создайте файл класса с именем ILogger.cs и замените код по умолчанию следующим кодом:

    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);
    
        }
    }
    

    Интерфейс предоставляет три уровня трассировки для указания относительной важности журналов, а также для предоставления сведений о задержке для вызовов внешних служб, таких как запросы к базе данных. Методы ведения журнала имеют перегрузки, которые позволяют передавать исключение. Таким образом, сведения об исключении, включая трассировку стека и внутренние исключения, надежно регистрируются классом, реализующим интерфейс, вместо того, чтобы полагаться на это в каждом вызове метода ведения журнала во всем приложении.

    Методы TraceApi позволяют отслеживать задержку каждого вызова внешней службы, например База данных SQL.

  3. В папке ведения журнала создайте файл класса с именем Logger.cs и замените код по умолчанию следующим кодом:

    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();
        }
      }
    }
    

Реализация используется System.Diagnostics для выполнения трассировки. Это встроенная функция .NET, которая упрощает создание и использование сведений трассировки. Существует множество прослушивателей, которые можно использовать с System.Diagnostics трассировкой, для записи журналов в файлы, например, или для записи их в хранилище BLOB-объектов в Windows Azure. Ознакомьтесь с некоторыми параметрами и ссылками на другие ресурсы, чтобы получить дополнительные сведения об устранении неполадок с веб-сайтами Windows Azure в Visual Studio. В этом руководстве вы увидите только журналы в окне вывода Visual Studio.

В рабочем приложении может потребоваться использовать платформы трассировки, отличные System.Diagnosticsот других, и ILogger интерфейс упрощает переход на другой механизм трассировки, если вы решите сделать это.

Создание классов перехватчика

Затем вы создадите классы, которые Entity Framework будет вызывать каждый раз при отправке запроса в базу данных, один для имитации временных ошибок и один для ведения журнала. Эти классы перехватчика должны быть производными от DbCommandInterceptor класса. В них выполняется переопределение метода, которое автоматически вызывается при выполнении запроса. В этих методах можно проверить или записать запрос, отправляемый в базу данных, и вы можете изменить запрос перед отправкой в базу данных или вернуть что-то в Entity Framework самостоятельно, даже не передавая запрос в базу данных.

  1. Чтобы создать класс перехватчика, который будет регистрировать каждый SQL-запрос перед отправкой в базу данных, создайте файл класса с именем InterceptorLogging.cs в папке логики и замените код по умолчанию следующим кодом:

    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);
        }
      }
    }
    

    Для успешных запросов или команд этот код записывает журнал сведений с сведениями о задержке. Для исключений создается журнал ошибок.

  2. Чтобы создать класс перехватчика, который создаст фиктивные временные ошибки при вводе "Throw" в текстовом поле "Имя" на странице с именем AdminPage.aspx, создайте файл класса с именем InterceptorTransientErrors.cs в папке логики и замените код по умолчанию следующим кодом:

    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;
        }
      }
    }
    

    Этот код переопределяет ReaderExecuting метод, который вызывается для запросов, которые могут возвращать несколько строк данных. Если вы хотите проверить устойчивость подключения для других типов запросов, можно также переопределить NonQueryExecuting методы и ScalarExecuting методы, так как перехватчик ведения журнала делает.

    Позже вы войдете в систему в качестве администратора и выберите ссылку "Администратор " на верхней панели навигации. Затем на странице AdminPage.aspx вы добавите продукт с именем Throw. Код создает фиктивное База данных SQL исключение для номера ошибки 20, тип, который обычно является временным. Другие номера ошибок, которые в настоящее время распознаются как временные: 64, 233, 10053, 10054, 10060, 10928, 10929, 40197, 40501 и 40613, но они могут изменяться в новых версиях База данных SQL. Продукт будет переименован в TransientErrorExample, который можно следовать в коде файла InterceptorTransientErrors.cs .

    Код возвращает исключение в Entity Framework вместо выполнения запроса и передачи результатов обратно. Временное исключение возвращается четыре раза, а затем код возвращается к обычной процедуре передачи запроса в базу данных.

    Так как все регистрируется в журнале, вы сможете увидеть, что Entity Framework пытается выполнить запрос четыре раза, прежде чем завершиться успешно, и единственное различие в приложении заключается в том, что для отрисовки страницы с результатами запроса требуется больше времени.

    Количество повторных попыток, которые будут настраиваться в Entity Framework; Код указывает четыре раза, так как это значение по умолчанию для политики выполнения База данных SQL. При изменении политики выполнения вы также измените код здесь, указывающий, сколько раз создаются временные ошибки. Вы также можете изменить код, чтобы создать больше исключений, чтобы Entity Framework вызовет RetryLimitExceededException исключение.

  3. В Global.asax добавьте следующие инструкции using:

    using System.Data.Entity.Infrastructure.Interception;
    
  4. Затем добавьте выделенные строки в Application_Start метод:

    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());
      
    }
    

Эти строки кода вызывают выполнение кода перехватчика, когда Entity Framework отправляет запросы в базу данных. Обратите внимание, что поскольку вы создали отдельные классы перехватчика для моделирования временных ошибок и ведения журнала, вы можете независимо включить и отключить их.

Вы можете добавлять перехватчики с помощью DbInterception.Add метода в любом месте кода. Он не должен находиться в методе Application_Start . Другим вариантом, если вы не добавили перехватчики в Application_Start метод, необходимо обновить или добавить класс с именем WingtipToysConfiguration.cs и поместить приведенный выше код в конец конструктора WingtipToysConfiguration класса.

Где бы вы ни положили этот код, будьте осторожны, чтобы не выполняться DbInterception.Add для одного перехватчика несколько раз, или вы получите дополнительные экземпляры перехватчика. Например, если добавить перехватчик ведения журнала дважды, вы увидите два журнала для каждого запроса SQL.

Перехватчики выполняются в порядке регистрации (порядок DbInterception.Add вызова метода). Порядок может иметь значение в зависимости от того, что вы делаете в перехватчике. Например, перехватчик может изменить команду SQL, которую он получает в свойстве CommandText . При изменении команды SQL следующий перехватчик получит измененную команду SQL, а не исходную команду SQL.

Вы написали временный код моделирования ошибок таким образом, что позволяет вызывать временные ошибки, вводя другое значение в пользовательском интерфейсе. В качестве альтернативы можно написать код перехватчика, чтобы всегда создавать последовательность временных исключений без проверки определенного значения параметра. Затем можно добавить перехватчик только в том случае, если требуется создать временные ошибки. Однако при этом не добавляйте перехватчик до завершения инициализации базы данных. Другими словами, выполните по крайней мере одну операцию базы данных, например запрос к одному из наборов сущностей, прежде чем приступить к возникновению временных ошибок. Entity Framework выполняет несколько запросов во время инициализации базы данных, и они не выполняются в транзакции, поэтому ошибки во время инициализации могут привести к возникновению несогласованного состояния контекста.

Проверка устойчивости ведения журнала и подключения

  1. В Visual Studio нажмите клавишу F5 , чтобы запустить приложение в режиме отладки, а затем войдите в систему в качестве администратора с помощью pa$$word в качестве пароля.

  2. Выберите "Администратор" в верхней части панели навигации.

  3. Введите новый продукт с именем Throw с соответствующим описанием, ценой и файлом изображения.

  4. Нажмите кнопку "Добавить продукт ".
    Вы заметите, что браузер, кажется, зависает в течение нескольких секунд, пока Entity Framework повторяет запрос несколько раз. Первое повторение происходит очень быстро, то ожидание увеличивается до каждой дополнительной попытки. Этот процесс ожидания дольше, прежде чем каждая повторная попытка называется экспоненциальным обратным выходом .

  5. Подождите, пока страница больше не пытается загрузить.

  6. Остановите проект и просмотрите окно вывода Visual Studio, чтобы просмотреть выходные данные трассировки. Окно вывода можно найти, выбрав отладку ->Windows ->Output. Возможно, вам придется прокручивать несколько других журналов, написанных вашим средством ведения журнала.

    Обратите внимание, что фактические запросы SQL, отправленные в базу данных. Вы видите некоторые начальные запросы и команды, которые Entity Framework выполняет для начала работы, проверяя версию базы данных и таблицу журнала миграции.
    Окно вывода
    Обратите внимание, что этот тест нельзя повторить, если вы не остановите приложение и не перезапустите его. Если вы хотите проверить устойчивость подключений несколько раз в одном запуске приложения, можно написать код для сброса счетчика ошибок.InterceptorTransientErrors

  7. Чтобы увидеть разницу в стратегии выполнения (политики повторных попыток), закомментируйте SetExecutionStrategy строку в WingtipToysConfiguration.cs файле в папке логики , снова запустите страницу администрирования в режиме отладки и снова добавьте продукт с именем "Throw".

    На этот раз отладчик останавливается на первом созданном исключении сразу при попытке выполнить запрос в первый раз.
    Отладка — просмотр сведений

  8. Раскомментируйте SetExecutionStrategy строку в файле WingtipToysConfiguration.cs .

Итоги

В этом руководстве вы узнали, как изменить пример приложения веб-формы для поддержки устойчивости подключений и перехвата команд.

Next Steps

После проверки устойчивости подключения и перехвата команд в ASP.NET веб-формы ознакомьтесь с разделом асинхронные методы ASP.NET веб-формы в ASP.NET 4.5. В этом разделе описаны основы создания асинхронного приложения ASP.NET веб-формы с помощью Visual Studio.