• Microsoft .NET
  • ASP.NET
  • Создание динамического пользовательского интерфейса ASP.NET, управляемого данными

Создание динамического пользовательского интерфейса ASP.NET, управляемого данными - Сохранение значений специализированных атрибутов клиента

ОГЛАВЛЕНИЕ

Сохранение значений специализированных атрибутов клиента

На данном этапе страница ~/Customers/ClientCustomAttributes.aspx отображает интерфейсы для специализированных клиентских атрибутов и включает текущие, только что отредактированные, значения . Наконец, нам надо обновить изменения значений атрибутов. Страница ~/Customers/ClientCustomAttributes.aspx включает в себя кнопку Update, нажав которую вы обновите значения специализированных атрибутов клиента и затем пользователь будет перенаправлен на страницу управления клиентами (Manage Clients) (i.e., ~/Customers/Default.aspx).

Первым делом тут будет получение значений, которые пользователь ввел в различные интерфейсы специализированных атрибутов. Это выполнимо путем осуществления запроса к базе данных для того, чтобы получить список специализированных атрибутов для клиента и затем перечислить атрибуты используя метод FindControl(controlId) для того, чтобы программно получить доступ к элементу управления и получить его значение.

Следующий блок кода (который я нашел в обработчике события Click элемента Update Button) получает специализированный клиентский атрибут из базы данных, в цикле проходит по ним и для каждого атрибута вызывает метод GetValueForCustomAttribute чтобы получить пользовательский ввод в интерфейс. Вскоре мы рассмотрим данный метод.

'Получение специализированных атрибутов для пользователя
Dim myCommand As New SqlCommand
myCommand.Connection = myConnection
myCommand.Transaction = myTransaction
myCommand.CommandText = "SELECT a.DynamicAttributeId, a.DataTypeId " & _
                  "FROM DynamicAttributesForClients a " & _
                  "WHERE a.CustomerId = @CustomerId "
myCommand.Parameters.AddWithValue("@CustomerId", Helpers.GetCustomerIdForLoggedOnUser())

Dim myReader As SqlDataReader = myCommand.ExecuteReader

Dim AttributeValues As New Dictionary(Of Guid, SqlParameter)
While myReader.Read()
   Dim DynamicAttributeId As Guid = CType(myReader("DynamicAttributeId"), Guid)
   Dim DataTypeId As DataTypeIdEnum = CType(Convert.ToInt32(myReader("DataTypeId")), DataTypeIdEnum)

   AttributeValues(DynamicAttributeId) = GetValueForCustomAttribute(DynamicAttributeId, DataTypeId)
End While
myReader.Close() 

Значения, возвращеные из метода GetValueForCustomAttribute, хранятся в объекте Dictionary, названном AttributeValues. Объект Dictionary пригоден для хранения набора элементов, которые индексируются по какому-то значению, отличному от порядкового числа. В данном случае я хочу иметь коллекцию атрибутов, доступную при помощи AttributeId (значение Guid), чьими значениями являются объекты  SqlParameter с соответствующими значениями, введенными пользователем. Поэтому я создал объект Dictionary с ключами типа Guid и значения типа SqlParameter - Dim AttributeValues As New Dictionary(Of Guid, SqlParameter). (Если вам все еще непонятно, то все станет более ясно, как только мы далее рассмотрим код обработчика события Click, который на самом деле обновляет базу данных .)

Метод GetValueForCustomAttribute возвращает объект SqlParameter со значением ParameterName типа @DynamicValue и соответственно установленные свойства Value и DbType. Если клиент не введет значение, Value элемента SqlParameter - назначается значение NULL (DBNull.Value). Более того, свойство DbType установлено соответственно типу данных специализированного клиентского атрибута. Пользовательский интерфейс, используемый для определенного клиентского атрибута, возвращается посредством метода FindControl(controlId) (а именно, CustomUITable.FindControl(controlId)). Вспомните, что при создании элементов управления пользовательского интерфейса для специализированных клиентских атрибутов, мы настроим ID элемента управления в значение колонки DynamicAttributeId, форматированного при помощи метода GetID. Такая же логика используется для программного поиска элемента управления, тем самым мы можем возвратить значение, введенное пользователем.

Private Function GetValueForCustomAttribute(ByVal DynamicAttributeId As Guid, ByVal DataTypeId As DataTypeIdEnum) As SqlParameter
   Dim userInputParam As New SqlParameter
   userInputParam.ParameterName = "@DynamicValue"
   userInputParam.Value = DBNull.Value

   Dim ctrlId As String = GetID(DynamicAttributeId)
   Dim ctrl As Control = CustomUITable.FindControl(ctrlId)

   Select Case DataTypeId
      Case DataTypeIdEnum.String
         Dim tb As TextBox = CType(ctrl, TextBox)
         userInputParam.DbType = Data.DbType.String
         If Not String.IsNullOrEmpty(tb.Text) Then
            userInputParam.Value = tb.Text.Trim()
         End If

      Case DataTypeIdEnum.Boolean
         Dim cb As CheckBox = CType(ctrl, CheckBox)
         userInputParam.Value = cb.Checked
         userInputParam.DbType = Data.DbType.Boolean

      Case DataTypeIdEnum.Numeric
         Dim tb As TextBox = CType(ctrl, TextBox)
         userInputParam.DbType = Data.DbType.Double
         If Not String.IsNullOrEmpty(tb.Text) Then
            userInputParam.Value = tb.Text.Trim()
         End If

      Case DataTypeIdEnum.Date
         Dim tb As TextBox = CType(ctrl, TextBox)
         userInputParam.DbType = Data.DbType.Date
         If Not String.IsNullOrEmpty(tb.Text) Then
            userInputParam.Value = tb.Text.Trim()
         End If
   End Select

   Return userInputParam
End Function 

Так же, как и в случае с кодом, используемым для создания пользовательского интерфейса, логика, используемая в методе GetValueForCustomAttribute для получения пользовательского значения, является жестко запрограммированной в фоновом классе ASP.NET.

Как только все значения специализированных клиентских атрибутов будут загружены в объект AttributeValues Dictionary , мы будем готовы обновить базу данных. Каждое значение клиентского атрибута хранится в записи таблицы DynamicValuesForClients. Если всего существуют пять специализированных атрибутов, то каждый клиент, чьи атрибуты были сохранены, будет иметь пять записей в DynamicValuesForClients. При сохранении клиентских специализированных атрибутов нам наверняка понадобится:

  • Вставить записи в DynamicValuesForClients - если у клиента еще не были указаны значения ни одного специализированного атрибута, либо если новые специализированные клиентские атрибуты были добавлены в систему, то в DynamicValuesForClients не будет соответствующей записи для конкретного значения атрибута. Поэтому нам необходимо будет добавить новую запись в DynamicValuesForClients.
  • Обновить записи в DynamicValuesForClients - если клиент уже имеет запись в DynamicValuesForClients для определенного специализированного атрибута, обновление значения атрибута требует обновления соответствующей записи в базе данных.

Какой подход мы будем использовать - зависит от того, существует ли уже запись в таблице DynamicValuesForClients для пары ClientId и AttributeId. Я создал хранимую процедуру, названную lawfirm_AddOrUpdateDynamicValueForClient для обработки данного решения. Это упрощает код ASP.NET: просто в цикле пройти по объекту AttributeValues Dictionary и для каждого элемента вызвать хранимую процедуру lawfirm_AddOrUpdateDynamicValueForClient, передавая ClientId, AttributeId и значение для данного атрибута. (Поскольку элементы в объекте AttributeValues Dictionary являются объектами SqlParameter , то мы можем добавить их к набору Parameters SqlCommand используя myCommand.Parameters.Add(AttributeValues(attributeId)).)

For Each AttributeId As Guid In AttributeValues.Keys
   myCommand.CommandText = "lawfirm_AddOrUpdateDynamicValueForClient"
   myCommand.CommandType = Data.CommandType.StoredProcedure
   myCommand.Parameters.Clear()
   myCommand.Parameters.AddWithValue("@ClientId", Request.QueryString("ID"))
   myCommand.Parameters.AddWithValue("@AttributeId", AttributeId)
   myCommand.Parameters.Add(AttributeValues(AttributeId))

   myCommand.ExecuteNonQuery()
Next 

Далее вы увидите хранимую процедуру lawfirm_AddOrUpdateDynamicValueForClient. Она очень проста - в ней проверяется существование записи для переданной пары ClientId и AttributeId. В случае если она найдена, то запись уже существует в таблице, поэтому выражение UPDATE будет использовано; если запись не будет найдена, то необходимо ее добавить, тем самым использовав выражение INSERT.

CREATE PROCEDURE dbo.lawfirm_AddOrUpdateDynamicValueForClient
(
   @ClientId uniqueidentifier,
   @AttributeId uniqueidentifier,
   @DynamicValue sql_variant
)
AS

IF EXISTS(SELECT 1 FROM DynamicValuesForClients WHERE ClientId = @ClientId AND AttributeId = @AttributeId)
   -- Запись существует, потому ее надо обновить
   UPDATE DynamicValuesForClients SET
      DynamicValue = @DynamicValue
   WHERE ClientId = @ClientId AND AttributeId = @AttributeId
ELSE
   -- Запись не существует, потому надо ее вставить
   INSERT INTO DynamicValuesForClients(ClientId, AttributeId, DynamicValue)
   VALUES(@ClientId, @AttributeId, @DynamicValue) 

И это все! Я рекомендую вам загрузить пример, предложенный в конце статьи, и детально исследовать код страницы. Вы заметите, что весь набор SQL-выражений в обработчике события Click элемента Button, названного Update, расположен в одной транзакции. Данная техника гарантирует нам то, что набор выражений INSERT и/или UPDATE, которые происходят во  время обновления каждого значения специализированного клиентского атрибута, обрабатываются как атомарная операция.


Теперь у нас есть полностью динамическое веб-приложение, управляемое данными! Пользователи могут авторизироваться на сайте, указать специализированные клиентские атрибуты и управлять значениями для данных атрибутов. Конечно, мы все еще можем много чего модернизировать, и осталась нерешенной парочка вопросов. Например, что происходит в случае, если пользователь переопределит существующий специализированный атрибут? Как мы можем расширить систему таким образом, чтобы она включала в себя больше типов данных? Как добавить дополнительные атрибуты к типам данных, к примеру, индикатор того, что определенное строковое поле является обязательным или что значение даты должно быть в каких-либо пределах? В четвертой части данной серии мы обсудим похожие вопросы.