Конфигурирование профилей
152ASP.NET --- Безопасность в ASP.NET --- Конфигурирование профилей
Поставщик SqlProfileProvider позволяет сохранять информацию профилей в базе данных SQL Server 7.0 и более поздних версий. Создавать таблицы профилей можно в любой базе данных. Однако другие детали схемы базы данных изменять нельзя, т.е. вы привязаны к определенным именам таблиц, столбцов и форматам сериализации. Для использования профилей понадобится выполнить следующие шаги:
Создайте таблицы профиля. (Если используется SQL Server Express Edition, этот шаг выполняется автоматически.)
Сконфигурируйте поставщика.
Определите некоторые свойства профиля.
Включите аутентификацию для некоторой части веб-сайта.
Используйте свойства профилей в коде веб-страницы.
В последующих разделах мы разберем каждый из этих шагов.
Создание таблиц профилей
Если используется версия, отличная от SQL Server Express Edition, таблицы профилей понадобится создать вручную. Для этого необходимо применить утилиту командной строки aspnet_regsql.exe - тот же инструмент, который позволяет генерировать базы данных для других средств ASP.NET, таких как состояние сеанса на базе SQL Server, членство, роли, зависимости кэша базы данных и персонализация Web Parts. Утилита aspnet_regsql.exe находится в папке C:\Windows\Microsoft.NET\Framework\[Версия].
В случае применений SQL Server Express Edition создавать базу данных вручную не нужно. Вместо этого при первом использовании средства профилей ASP.NET создаст новую базу данных по имени aspnetdb.mdf, поместит ее в подкаталог App_Data веб-приложения и добавит таблицы профилей. Если база данных aspnetdb.mdf уже существует (поскольку она используется каким-то другим средством), то ASP.NET просто добавит к ней таблицы профилей.
Чтобы добавить таблицы, представления и хранимые процедуры, необходимые для профилей, используется утилита aspnet_regsql.exe с опцией командной строки -A p. Единственные детали, которые должны быть указаны помимо этой - местоположение сервера (-S), имя базы данных (-d) и информация аутентификации для подключения к базе данных (используйте -U и -P для указания имени и пароля пользователя либо -E, чтобы использовать текущую учетную запись Windows). Если не указывать местоположение сервера и имя базы данных, то aspnet_regsql.exe использует экземпляр по умолчанию на текущем компьютере, а также создает базу по имени aspnetdb.
Рассмотрим пример создания базы данных aspnetdb с именем по умолчанию на текущем компьютере за счет подключения к базе данных от имени текущей учетной записи Windows:
aspnet_regsql.exe -A p -E
В таблице ниже перечислены таблицы, которые создает aspnet_regsql.exe. (Некоторые редко используемые представления не включены.)
Имя таблицы | Описание |
---|---|
aspnet_Applications | Перечисление всех приложений, для которых существуют записи в этой базе данных. Допускается, чтобы несколько приложений ASP.NET использовали одну и ту же базу aspnetdb. В этом случае есть возможность отделить информацию профилей для каждого приложения (назначая каждому приложению отдельное имя при регистрации поставщика профилей) либо использовать одну и ту же информацию в разных приложениях совместно (назначая всем приложениям одно и то же имя) |
aspnet_Profile | Сохраняет специфичную для пользователя информацию профиля. В поле PropertyNames перечислены имена свойств, а в поле PropertyValuesString и PropertyValuesBinary - все данные профиля, хотя придется приложить некоторые усилия, если эту информацию понадобится разбирать в других программах (не ASP,NET). Каждая запись также включает дату и время последнего обновления (LastupdatedDate) |
aspnet_SchemaVersions | Перечисление всех поддерживаемых схем хранения информации о профилях. В будущем это должно позволить новым версиям ASP.NET предоставлять новые способы хранения информации профилей, не разрушая поддержку находящихся в эксплуатации старых баз данных |
aspnet_Users | Перечисляет имена пользователей с отображением их на одно из приложений в aspnet_Applications. Также фиксирует дату и время последнего запроса (LastActivityDate) и была ли запись сгенерирована автоматически для анонимного пользователя (IsAnonymous) |
Даже если имя базы данных по умолчанию (apsnetdb) не используется, должна применяться новая пустая база данных, в которой нет никаких других таблиц. Это объясняется тем, что aspnet_regsql.exe создает несколько таблиц для профилей и не стоит рисковать перемешивать их с бизнес-данными. Во всех последующих примерах предполагается использование базы данных apsnetdb.
На рисунке ниже показаны отношения между наиболее важными таблицами профилей. Среда ASP.NET также создает несколько хранимых процедур, облегчающих управление информацией в этих таблицах:
В таблице ниже перечислены наиболее важные хранимые процедуры:
Хранимая процедура | Описание |
---|---|
aspnet_Applications_CreateApplications | Проверяет наличие указанного имени приложения в таблице aspnet_Applications и при необходимости создает запись для нее |
aspnet_CheckSchemaVersion | Проверяет наличие поддержки специфического средства (такого как профили) в специфической версии схемы, используя для этого таблицу aspnet_SchemaVersions |
aspnet_Profile_GetProfiles | Извлекает имя пользователя и обновляет время во всех записях таблицы aspnet_Profile для определенного веб-приложения. Не возвращает никаких действительных данных профиля |
aspnet_Profile_GetProperties | Извлекает информацию профиля для определенного пользователя (который указан по имени). Информация никак не анализируется - эта хранимая процедура просто возвращает лежащие в основе поля (PropertyNames, PropertyValuesString, PropertyValuesBinary) |
aspnet_Profile_SetProperties | Устанавливает информацию профиля для определенного пользователя (указанного по имени). Этой хранимой процедуре требуются поля PropertyNames, PropertyValuesString и PropertyValuesBinary. Не существует возможности обновить отдельное свойство профиля |
aspnet_Profile_GetNumberOfInactiveProfiles | Возвращает записи профилей, которые не были востребованы в течение указанного вами времени |
aspnet_Profile_DeleteInactiveProfiles | Удаляет записи профилей, которые не были востребованы в течение указанного вами времени |
aspnet_Users_CreateUser | Создает новую запись в таблице aspnet_Users для определенного пользователя. Проверяет его существование (в этом случае никаких действий не предпринимается) и создает идентификатор GUID для использования в поле UserID, если таковой не указан |
aspnet_Users_DeleteUser | Удаляет определенную запись о пользователе из таблицы aspnet_Users |
Конфигурирование поставщика
Имея готовую базу данных, можно зарегистрировать SqlProfileProvider в файле web.config. Сначала для этого необходимо определить строку подключения к базе профилей. Затем в разделе <profile> следует удалить существующие поставщики (с помощью элемента <clear>) и добавить новый экземпляр класса System.Web.Profile.SqlProfileProvider (с помощью элемента <add>). Вот как должны выглядеть конфигурационные настройки:
<configuration>
<connectionStrings>
<add name="MyMembershipConnString" connectionString="Data Source=.\SQLEXPRESS;Initial Catalog=aspnetdb;Integrated Security=True"/>
</connectionStrings>
<system.web>
<profile defaultProvider="SqlProvider">
<providers>
<clear />
<add name="SqlProvider" type="System.Web.Profile.SqlProfileProvider"
connectionStringName="MyMembershipConnString"
applicationName="security" />
</providers>
</profile>
</system.web>
</configuration>
При определении поставщика профилей должно быть указано его имя (по которому элемент <profile> затем сможет обращаться к поставщику по умолчанию), точное имя типа, строка подключения и имя веб-приложения. Для разделения информации профилей между веб-приложениями применяйте различные имена приложений (или указывайте одно и то же имя, чтобы использовать информацию совместно).
Определение свойств профиля
Прежде чем сохранять что-либо в таблице aspnet_Profile, оно должно быть специальным образом определено. Это делается добавлением элемента <properties> в раздел <profile> файла web.config. Внутри элемента <properties> должно быть предусмотрено по одному дескриптору <add> для каждого специфичного в отношении пользователя фрагмента информации.
Как минимум, для элемента <add> должно быть указано имя свойства:
<profile defaultProvider="SqlProvider">
<providers>
...
</providers>
<properties>
<add name="FirstName"/>
<add name="LastName"/>
</properties>
</profile>
Обычно также указывается тип данных (если этого не сделать, предполагается, что свойство является строковым). В качестве типа можно задать любой сериализуемый класс:
<profile defaultProvider="SqlProvider">
<providers>
...
</providers>
<properties>
<add name="FirstName" type="String"/>
<add name="LastName" type="String"/>
<add name="DateOfBirth" type="DateTime"/>
</properties>
</profile>
Устанавливая еще несколько атрибутов, можно создавать расширенные свойства. Эти атрибуты перечислены в таблице ниже:
Атрибут (для элемента <add>) | Описание |
---|---|
name | Имя свойства |
type | Полное квалифицированное имя класса, представляющего тип свойства. По умолчанию - System.String |
serializeAs | Включает формат, используемый при сериализации (String, Binary, Xml или ProviderSpecific) |
readonly | Этот атрибут со значением true позволяет создать свойство, которое может быть прочитано, но не изменено (попытка изменить свойство вызовет ошибку во время компиляции). По умолчанию имеет значение false |
defaultValue | Значение по умолчанию, которое будет использовано, если профиль не существует либо не включает определенного фрагмента информации. Значение по умолчанию не затрагивает сериализацию. Если свойство профиля установлено, ProfileModule все равно запишет текущие значения в базу данных, даже если они совпадают со значениями по умолчанию |
allowAnonymous | Булевское значение, указывающее, должно ли данное свойство применяться со средством анонимных профилей, о котором будет сказано ниже. По умолчанию равно false |
provider | Поставщик профилей, который должен быть использован для управления только данным свойством. По умолчанию все свойства управляются с применением поставщика, указанного в элементе <profile>, но для разных свойств можно назначить разных поставщиков |
Использование свойств профилей
Поскольку свойства сохраняются в специфичных для пользователей свойствах, текущий пользователь должен быть аутентифицирован перед чтением или записью информации профиля. Можно применять систему аутентификации любого типа (Windows, с помощью форм или специальной). Понадобится только добавить правило авторизации, предотвращающее анонимный доступ к странице или папке, где планируется использование профиля. Ниже приведен пример:
<system.web>
<authentication mode="Forms">
<forms loginUrl="MyLogin.aspx"/>
</authentication>
<authorization>
<deny users="?" />
</authorization>
</system.web>
Правила авторизации и аутентификации более подробно рассматривались ранее.
Когда упомянутые детали на месте, все готово к тому, чтобы обращаться к информации профиля, используя свойство Profile текущей страницы. При запуске приложения ASP.NET создает новый класс для представления профиля, наследуя его от ProfileBase, который служит оболочкой для коллекции настроек профиля. ASP.NET добавляет к этому классу по одному строго типизированному свойству для каждого свойства профиля, определенного в файле web.config. Эти строго типизированные свойства просто вызывают методы GetPropertyValue() и SetPropertyValue() базового класса ProfileBase для извлечения и установки соответствующих значений свойств.
Например, если определить строковое свойство по имени FirstName, то его значение на странице можно устанавливать следующим образом:
Profile.FirstName = "Ivan";
Ниже представлена полная тестовая страница, которая позволяет отображать информацию профиля для текущего пользователя или устанавливать новую информацию профиля:
<body>
<form id="form1" runat="server">
<div>
<table>
<tr>
<td style="width: 99px">Имя:
</td>
<td>
<asp:TextBox ID="txtFirst" runat="server">Ivan</asp:TextBox></td>
</tr>
<tr>
<td style="width: 99px">Фамилия:</td>
<td>
<asp:TextBox ID="txtLast" runat="server">Ivanov</asp:TextBox></td>
</tr>
<tr>
<td style="width: 99px; height: 182px">Дата рождения:
</td>
<td style="height: 182px">
<asp:Calendar ID="Calendar1" runat="server"></asp:Calendar>
</td>
</tr>
</table>
<br />
<br />
<asp:Button ID="cmdShow" runat="server" OnClick="cmdShow_Click" Text="Показать данные профиля" />
<asp:Button ID="cmdSet" runat="server" OnClick="cmdSet_Click" Text="Установить данные профиля" /><br />
<br />
<br />
<div style="background-color: lightyellow; border-right: 2px solid; border-top: 2px solid; border-left: 2px solid; border-bottom: 2px solid;">
<asp:Label ID="lbl" runat="server" EnableViewState="False" Font-Bold="True"></asp:Label>
</div>
</div>
</form>
</body>
При первом запуске этой страницы никакой информации профилей не извлекается и никакие подключения к базе данных не используются. Однако если щелкнуть на кнопке "Показать данные профиля", информация профиля извлекается и отображается на странице:
protected void cmdShow_Click(object sender, EventArgs e)
{
lbl.Text = "First Name: " + Profile.FirstName + "<br />" +
"Last Name: " + Profile.LastName + "<br />" +
"Date of Birth: " + Profile.DateOfBirth.ToString();
}
Если в этот момент база данных профиля не существует или соединение не может быть установлено, возникает ошибка. В противном случае страница выполняется, и появляется новая, извлеченная из профиля информация. Формально полный профиль извлекается, когда код в первой строке обращается к свойству Profile.FirstName, и используется в последующих операторах кода.
Свойства профиля ведут себя подобно любой другой переменной-члену класса. Это значит, что чтение значения профиля, которое не было установлено, дает в результате значение по умолчанию (такое как пустая строка или 0).
Щелчок на кнопке "Установить данные профиля" приводит к установке информации профиля на основе текущих значений в элементах управления:
protected void cmdSet_Click(object sender, EventArgs e)
{
Profile.FirstName = txtFirst.Text;
Profile.LastName = txtLast.Text;
Profile.DateOfBirth = Calendar1.SelectedDate;
}
Теперь информация профиля фиксируется в базе данных по завершении запроса страницы. Если необходимо зафиксировать часть или всю информацию раньше этого момента (возможно, несколько раз обратившись к базе данных), просто вызовите метод Profile.Save(). Как видите, средство профилей непревзойденно по своей простоте.
Сериализация профиля
Ранее было показано, как свойства сериализуются в одну строку. Например, при сохранении свойства FisrtName, равного "Ivan", и LastName, равного "Petrov" оба значения соединяются вместе в свойстве PropertyValuesString, экономя пространство:
IvanPetrov
Поле PropertyNames предоставляет информацию, которая необходима для разбора значений в поле PropertyValuesString. Вот что находится в поле PropertyNames в данном примере:
FirstName:S:0:4:LastName:S:4:6:
Двоеточия (:) служат разделителями. Нечто интересное происходит, когда создается профиль с данными типа DataTime. Заглянув в поле PropertyValuesString, в нем можно увидеть примерно такое значение:
<?xml version="1.0" encoding="utf-16"?> <dateTime>2009-11-05T00:00:00</dateTime>
На первый взгляд это выглядит так, как если бы данные профиля были сериализованы в виде XML, но PropertyValuesString очевидно не содержит допустимый документ (из-за текста в конце). На самом деле здесь происходит вот что: первая часть информации - DateTime - сериализована по умолчанию как XML. Следующие два свойства профиля сериализованы как обычные строки. Поле PropertyName несколько все проясняет:
FirstName:S:0:4:LastName:S:4:6:DateOfBirth:S:10:81:
Интересно, что формат сериализации любого свойства профиля может быть изменен простым добавлением атрибута serializeAs к его объявлению в файле web.config. Возможные значения перечислены в таблице ниже:
serializeAs | Описание |
---|---|
String | Преобразует тип в строковое представление. Принимает конвертер типа, который может выполнить эту работу. |
Xml | Преобразует тип в XML-представление, сохраняющееся в виде строки, с применением System.Xml.XmlSerialization.XmlSerializer (тот же класс, что используется веб-службами) |
Binary | Преобразует тип в соответствующее двоичное представление, понятное только .NET с использованием System.Runtime.Serialization.Formatters.Binary.BinaryFormatter. Это наиболее компактная форма, но и наименее гибкая. Двоичные данные сохраняются в поле PropertyValuesBinary вместо PropertyValues |
ProviderSpecific | Выполняет специальную сериапизацию, реализуемую пользовательским поставщиком |
Ниже показан пример изменения сериализации настроек профиля:
<properties>
<add name="FirstName" type="String" serializeAs="Xml"/>
<add name="LastName" type="String" serializeAs="Xml"/>
<add name="DateOfBirth" type="DateTime" serializeAs="String"/>
</properties>
Теперь при следующей установке профиля сериализованное представление в поле PropertyValuesString примет такую форму:
<?xml version="1.0" encoding="utf-16"?> <string>Ivan</string> <?xml version="1.0" encoding="utf-16"?> <string>Petrov</string> 2009-11-05
Если используется режим двоичной сериализации, значение свойства будет помещено в поле PropertyValuesBinary вместо PropertyValuesString. Единственным признаком этого смещения является наличие буквы B вместо S в поле PropertyNames.
Все эти детали сериализации приводят к важному вопросу: что происходит, когда изменяются свойства профиля или способ их сериализации? Свойства профилей не имеют никакой поддержки версий. Тем не менее, свойства можно добавлять или удалять с относительно незначительными последствиями. Например, ProfileModule будет игнорировать свойства, которые представлены в таблице aspnet_Profile, но не определены в файле web.config. При модификации части профиля в следующий раз эти свойства будут заменены новой информацией.
Аналогично, если профиль определяется в файле web.config, который отсутствует в сериализованной информации профиля, то ProfileModule использует только значения по умолчанию. Однако более серьезные изменения вроде переименования свойства, изменения его типа данных и тому подобного, весьма вероятно, вызовут исключение при попытке чтения информации профиля. Хуже того, поскольку сериализованный формат фиксирован, не существует простого способа миграции существующих данных профиля в его новую структуру.
Не все типы могут быть сериализованы всеми способами. Например, классы, которые не содержат конструкторы без параметров, не могут быть сериализованы в режиме Xml. Классы, не имеющие атрибута Serializable, не могут быть сериализованы в режиме Binary. Более подробно эти исключения будут указаны, когда речь пойдет о пользовательских типах в профилях, а пока просто имейте в виду, что обойти сериализацию типов, которые поддаются сериализации, можно только при выборе определенного ее режима.
Группы профилей
Когда в профиле имеется большое количество настроек, часть из которых логически связана между собой, то для их лучшей организации можно воспользоваться группами профилей.
Например, могут существовать свойства, относящиеся к данным пользователей, и другие, относящиеся к информации о поставках. Ниже показано, как организовать эти свойства профилей с помощью элемента <group>:
<properties>
<group name="Data">
<add name="FirstName" type="String"/>
<add name="LastName" type="String"/>
<add name="DateOfBirth" type="DateTime"/>
</group>
<group name="Address">
<add name="Street" type="String"/>
<add name="City" type="String"/>
<add name="ZipCode" type="String"/>
<add name="Country" type="String"/>
</group>
</properties>
После этого обращаться к свойствам в коде можно с указанием имени группы. Например, вот как извлечь информацию о стране:
lblCountry.Text = Profile.Address.Country;
Группы - это всего лишь ограниченная замена полноценного пользовательского класса или структуры. Например, того же самого эффекта, что и в предыдущем примере, можно достичь, объявив собственный класс Address. Также есть возможность добавлять и другие средства (такие как проверка достоверности в процедурах свойств). В следующем разделе будет показано, как это делается.
Профили и пользовательские типы данных
Использовать собственный класс в профиле очень легко. Для этого необходимо начать с создания класса, который служит оболочкой для необходимой информации. В этом классе можно применять общедоступные переменные-члены или полноценные процедуры свойств. Несмотря на свою сложность, последний вариант предпочтительнее, поскольку он гарантирует, что класс будет поддерживать привязку данных, и обеспечит достаточную гибкость для добавления кода процедур позднее.
Ниже показана сокращенная версия класса Address, связывающего вместе всю информацию, которая была представлена в предыдущем примере. Для краткости здесь используются автоматические свойства (которые сохраняют данные в автоматически сгенерированных приватных полях):
[Serializable]
public class Address
{
public string Name { get; set; }
public string Street { get; set; }
public string City { get; set; }
public string ZipCode { get; set; }
public string Country { get; set; }
public Address(string name, string street, string city, int zipCode, string country)
{
Name = name;
Street = street;
City = city;
ZipCode = zipCode.ToString();
Country = country;
}
public Address() { }
}
Этот класс можно поместить в каталог App_Code (или скомпилировать в DLL-сборку в каталоге Bin). Последний шаг - добавление свойства, которое его использует:
<properties>
<add name="FirstName" type="String" serializeAs="Xml"/>
<add name="LastName" type="String" serializeAs="Xml"/>
<add name="DateOfBirth" type="DateTime" serializeAs="String"/>
<add name="Address" type="Address"/>
</properties>
После этого классом можно манипулировать в коде:
Profile.Address =
new Address(txtFirst.Text, "Ленинский проспект 68", "Moscow", 119296, "Russia");
Сериализация пользовательских типов
Следует иметь в виду несколько моментов, в зависимости от того, как решено сериализировать свой класс. По умолчанию все пользовательские типы данных применяют XML-сериализацию с XmlSerializer. Этот класс относительно ограничен в своей способности к сериализации. Он просто копирует значение из каждого общедоступного свойства или переменной-члена в простой формат XML, наподобие следующего:
<?xml version="1.0" encoding="utf-16"?>
<Address xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>Ivan</Name>
<Street>Ленинский проспект 68</Street>
<City>Moscow</City>
<ZipCode>119296</ZipCode>
<Country>Russia</Country>
</Address>
Это XML-представление можно изменить за счет добавления к общедоступным свойствам класса атрибутов из пространства имен System.Xml.Serialization. Например, можно использовать XmlElement для изменения имени элемента XML, применяемого для хранения свойства, XmlAttribute - для того, чтобы обеспечить сохранение свойства в виде атрибута, а не элемента XML, и XmlIgnore - для предотвращения сериализации значения свойства.
При десериализации класса XmlSerializer должен иметь возможность найти общедоступный конструктор без параметров. Вдобавок ни одно из свойств не должно иметь доступ только для чтения. Если нарушить любое из этих двух правил, процесс десериализации не пройдет.
Если вы решите использовать двоичную сериализацию вместо XmlSerialization, то .NET применит совершенно другой подход:
<add name="Address" type="Address" serializeAs="Binary"/>
В этом случае ProfileModule обратится за помощью к BinaryFormatter. Класс BinaryFormatter может сериализовать общедоступное и приватное содержимое любого класса, если этот класс снабжен атрибутом Serializable. (При этом все классы, от которых он унаследован, и классы, на которые он ссылается, также должны быть сериализуемыми.)
И, наконец, можно использовать строковую сериализацию:
<add name="Address" type="Address" serializeAs="String"/>
В этом случае понадобится конвертер типа, который может преобразовывать информацию между экземпляром класса и его строковым представлением.
Автоматическое сохранение
Модуль ProfileModule, сохраняющий информацию профилей, не имеет возможности обнаруживать изменения в сложных типах данных (не относящихся к строкам, простым числовым типам, булевским значениям и т.д.). Это значит, что если профиль включает в себя сложные типы данных, то ProfileModule будет сохранять информацию профиля в конце каждого запроса, который обращается к объекту Profile.
Очевидно, что такой подход влечет за собой излишние накладные расходы. Для оптимизации производительности при работе со сложными типами доступно несколько вариантов. Один из них - сделать соответствующее свойство профиля доступным только для чтения (если известно, что оно никогда не будет изменяться). Другой подход состоит в полном отключении автоматического сохранения за счет добавления атрибута automaticSaveEnabled к элементу <profile> и установки его в false, как показано ниже:
<profile defaultProvider="SqlProvider" automaticSaveEnabled="false"> ... </profile>
При таком подходе на вас возлагается ответственность за вызов Profile.Save() для явной фиксации изменений. В общем случае этот подход наиболее удобен, поскольку охватить все места в коде, где выполняется модификация профиля, довольно просто. Просто поместите в конце вызов Profile.Save():
protected void cmdSet_Click(object sender, EventArgs e)
{
Profile.FirstName = txtFirst.Text;
Profile.LastName = txtLast.Text;
Profile.DateOfBirth = Calendar1.SelectedDate;
Profile.Address = new Address(txtFirst.Text, "Ленинский проспект 68", "Moscow", 119296, "Russia");
Profile.Save();
}
И последний вариант предусматривает обработку события ProfileModule.ProfileAutoSaving в файле global.asax. В этом месте можно проверить, действительно ли необходимо сохранение, и отменить его, если оно не нужно.
Очевидная проблема здесь заключается в определении того, должно ли автоматическое сохранение быть отменено. Исходные данные профиля можно поместить в память и затем сравнивать эти объекты с текущим их состоянием, когда поступает событие ProfileAutoSaving. Однако этот подход довольно неуклюжий и медленный. Лучше сделать так, чтобы страница самостоятельно отслеживала, когда происходят изменения. Если изменение произошло, код может выставлять флаг, указывающий на необходимость обновления.
Например, рассмотрим тестовую страницу, показанную ниже, которая позволяет извлекать и модифицировать информацию адреса:
Все текстовые поля на этой странице используют один и тот же обработчик события TextChanged. Этот обработчик отражает то, что изменение было выполнено, сохраняя булевское значение в контексте текущего запроса:
protected void txt_TextChanged(object sender, EventArgs e)
{
Context.Items["AddressDirtyFlag"] = true;
}
Свойство Page.Context предоставляет объект HttpContext. Коллекция HttpContext.Items является удобным местом для временного хранения данных, которые должны быть использованы позднее во время той же самой обратной отправки. Для получения аналогичного эффекта можно использовать состояние представления и состояние сеанса, однако эти подходы предполагают более длительное хранение.
Имейте в виду, что значение, сохраняемое таким способом, остается только на протяжении текущего запроса. В данном примере это не проблема, потому что пользователь имеет только два выбора после внесения изменений - отклонить его (щелкнув на кнопке "Получить"), или сохранить (щелкнув на кнопке "Сохранить"). Однако если создается страница, на которой пользователь может вносить изменения в виде нескольких шагов, и применять их позднее, то придется немного потрудиться, чтобы реализовать логику сопровождения флага. Помещение флага в другое место, такое как состояние сеанса или состояние представления, работать не будет, т.к. ни то, ни другое не доступно, когда инициируется событие AutoSaving в файле global.asax.
И, наконец, ниже показан обработчик событий, который понадобится для выполнения автоматического сохранения только в случае, если были внесены изменения:
void Profile_ProfileAutoSaving(object sender, ProfileAutoSaveEventArgs e)
{
if ((e.Context.Items["AddressDirtyFlag"] == null) ||
(bool)e.Context.Items["AddressDirtyFlag"] == false)
{
e.ContinueWithProfileAutoSave = false;
}
}
Вспомните, что событие Profile.AutoSaving инициируется для любого изменения. При наличии более одной страницы, которые модифицируют различные детали профиля, возможно, придется написать условный код, проверяющий, какая именно страница была запрошена, и соответствующим образом разрешать или запрещать сохранение. В такой ситуации обычно проще вообще отключить автоматическое сохранение и принудительно вызывать метод Profile.Save().