#
Как использовать
Сериализация — процесс перевода структуры данных в последовательность байтов. Мы можем производить сериализацию как простых типов, так и моделей объектов реального мира, которые описываются в виде классов, называются бизнес-объектами или сущностями, и отвечают следующим условиям:
- сериализуеммый класс содержит открытый конструктор без параметров;
- каждому классу бизнес-объекта сопоставлен
BusinessObjectId
— уникальный целочисленный идентификатора типаulong
; - сериализуемые свойства класса являются публичными и имеют сопоставление с
TagId
— уникальным целочисленным идентификатором свойства в рамках класса бизнес-объекта (имеет типulong
).
Сопоставления BusinessObjectId
и TagId
осуществляются путём аннотация данных с помощью атрибутов или средствами Fluent API.
#
Бизнес-объекты и аннотация данных с помощью атрибутов
Атрибуты предоставляют мощное средство для связывания метаданных или декларативной информации с кодом и могут быть использованы для сопоставления идентификаторов классов и свойств бизнес-объектов. В случае если вы имеете полный контроль над кодом, этот подход может оказаться наиболее предпочтительным.
Для этого подхода определены следующие атрибуты:
BusinessObjectIdAttribute
— задаёт значение уникального идентификатора класса бизнес-объекта (используется при сериализации, для десериализации будет проигнорировано);TagIdAttribute
— задаёт значение уникального идентификатора свойства в рамках класса;DateTimeEncodingRuleAttribute
— для свойств с типамиDateTime
иDateTimeOffset
позволяет определить правило кодирования (DateOnly, DateTime, DateTimeOffset);EnumAsStringAttribute
— для свойств с типом перечисление (enum
) задает правило кодирования в виде строки в кодировке UTF-8 (по-умолчанию используется целочисленный базовый тип данных);StringEncodingRuleAttribute
— для свойств с типом строка (string
) задает кодировку, в которую будет преобразован текст перед его кодированием (в случае ошибки преобразования и включенном режимеSafeMode
данные будут сохранены в кодировке UTF-8).
Выберем в качестве объекта реального мира пропуск для доступа к объектам социальной инфраструктуры и опишем его модель в виде класса ExtraCityPass
с использованием атрибутов.
[BusinessObjectId(1)]
public class ExtraCityPass1
{
[TagId(1)]
public int Id { get; set; }
[TagId(2)]
[EnumAsString]
public CountryCode1 Nationality { get; set; }
[TagId(3)]
[StringEncodingRule(StringEncodingMode.Numeric)]
public string PassportNumber { get; set; }
[TagId(4)]
[StringEncodingRule(StringEncodingMode.Predefined, SafeMode = true)]
public string FullName { get; set; }
[TagId(5)]
public Gender1 Sex { get; set; }
[TagId(6)]
[DateTimeEncodingRule(DateTimeEncodingMode.DateOnly)]
public DateTime DateOfBirth { get; set; }
[TagId(7)]
public string Address { get; set; }
[TagId(8)]
[StringEncodingRule(StringEncodingMode.Numeric)]
public string MobileId { get; set; }
[TagId(9)]
public string Email { get; set; }
[TagId(10)]
public bool IsVactinated { get; set; }
public string Authority { get; set; }
[TagId(12)]
[DateTimeEncodingRule(DateTimeEncodingMode.DateTimeOffset)]
public DateTime NotValidAfter { get; set; }
[TagId(63)]
public byte[] Signature { get; set; }
}
// Dependent Elements
public enum Gender1 : byte
{
None = 0,
Male,
Female
}
public enum CountryCode1
{
None = 0,
GBR,
CHN,
KAZ,
RUS,
UKR,
USA,
}
#
Бизнес-объекты и Fluent API
Не всегда мы можем воспользоваться методом аннотации данных с помощью атрибутов и применить его к требуемому классу бизнес-объекта, как правило это обусловлено следующими факторами:
- нам необходимо работать с уже существующим классом из чужой сборки (например, другого разработчика);
- требуется динамическое сопоставление
BusinessObjectId
иTagId
; - требования политики информационной безопасности предприятия.
В этом случае мы оперируем классом бизнес-объекта который, как минимум, имеет открытый конструктор без параметров и публичные свойства:
public class ExtraCityPass2
{
public int Id { get; set; }
public CountryCode2 Nationality { get; set; }
public string PassportNumber { get; set; }
public string FullName { get; set; }
public Gender2 Sex { get; set; }
public DateTime DateOfBirth { get; set; }
public string Address { get; set; }
public string MobileId { get; set; }
public string Email { get; set; }
public bool IsVactinated { get; set; }
public string Authority { get; set; }
public DateTime NotValidAfter { get; set; }
public byte[] Signature { get; set; }
}
// Dependent Elements
public enum Gender2 : byte
{
None = 0,
Male,
Female
}
public enum CountryCode2
{
None = 0,
GBR,
CHN,
KAZ,
RUS,
UKR,
USA,
}
Для работы с классом бизнес-объекта, не имеющим аннотацию данных, мы должны подготовить декларативную информацию с помощью классов BusinessObjectConfiguration
и PropertyConfiguration
, и передать её экземпляру сериализатора используя метод AddBusinessObjectConfiguration()
.
С помощью метода GetProperty()
класса BusinessObjectConfiguration
мы получаем доступ к цепочке методов конфигурирования свойства класса, аналогичных по функциональности атрибутам аннотации данных:
SetTagId()
;SetDateTimeEncodingRule()
;SetEnumAsString()
;SetStringEncodingRule()
.
Подготовим и применим ту же декларативную информацию для нашего класса ExtraCityPass
альтернативным способом:
var conf2 = new BusinessObjectConfiguration<ExtraCityPass2>(id: 2);
conf2.GetProperty(nameof(ExtraCityPass2.Id))
.SetTagId(1)
.SetStringEncodingRule(StringEncodingMode.Numeric);
conf2.GetProperty(nameof(ExtraCityPass2.Nationality))
.SetTagId(2)
.SetEnumAsString();
conf2.GetProperty(nameof(ExtraCityPass2.PassportNumber))
.SetTagId(3)
.SetStringEncodingRule(StringEncodingMode.Numeric);
conf2.GetProperty(nameof(ExtraCityPass2.FullName))
.SetTagId(4)
.SetStringEncodingRule(StringEncodingMode.Predefined, SafeMode = true);
conf2.GetProperty(nameof(ExtraCityPass2.Sex))
.SetTagId(5);
conf2.GetProperty(nameof(ExtraCityPass2.DateOfBirth))
.SetTagId(6)
.SetDateTimeEncodingRule(DateTimeEncodingMode.DateOnly);
conf2.GetProperty(nameof(ExtraCityPass2.Address))
.SetTagId(7);
conf2.GetProperty(nameof(ExtraCityPass2.MobileId))
.SetTagId(8)
.SetStringEncodingRule(StringEncodingMode.Numeric);
conf2.GetProperty(nameof(ExtraCityPass2.Email))
.SetTagId(9);
conf2.GetProperty(nameof(ExtraCityPass2.IsVactinated))
.SetTagId(10);
conf2.GetProperty(nameof(ExtraCityPass2.NotValidAfter))
.SetTagId(12)
.SetDateTimeEncodingRule(DateTimeEncodingMode.DateTimeOffset);
conf2.GetProperty(nameof(ExtraCityPass2.Signature))
.SetTagId(63);
conf2.SyncTags();
//serializer.AddBusinessObjectConfiguration(conf2);
С помощью метода SyncTags()
класса BusinessObjectConfiguration
осуществляется синхронизация уникальных идентификаторов свойств, возможно это выглядит как "костыль" и в дальнейшем эта функциональность будет изменена.
#
Пример сериализации и десериализации бизнес-объекта
Продемонстрируем работу бинарного сериализатора на примере уже определенных классов ExtraCityPass1
и ExtraCityPass2
, а также подготовленной декларативной информации, содержащейся в переменной conf2
.
Инициализируем экземпляр класса ExtraCityPass1
и наполним его демонстрационными данными:
var obj = new ExtraCityPass1
{
Id = 7714377,
Nationality = CountryCode.USA,
PassportNumber = "2571459",
FullName = "Elvis Aaron Presley",
Sex = Gender.Male,
DateOfBirth = new DateTime(1935, 1, 8),
Address = "3764 Elvis Presley Boulevard, Memphis, TN 38116",
MobileId = "19013323322",
Email = "elvis@example.com",
IsVactinated = false,
Authority = "Tennessee DMV",
NotValidAfter = new DateTime(1977, 8, 16),
};
#
Сериализация
var serializer = new XyloCode.BusinessData.Serializer();
serializer.DefaultCharacterEncoding = Encoding.Latin1;
byte[] data = serializer.Encode(obj);
Console.WriteLine(data.Length);
> 153
Данные из переменной data
будут использованы для последующих примеров.
#
Десериализация в класс бизнес-объекта с аннотацией данных с помощью атрибутов
var serializer = new XyloCode.BusinessData.Serializer();
serializer.DefaultCharacterEncoding = Encoding.Latin1;
ExtraCityPass1 newObj = serializer.Decode<ExtraCityPass1>(data);
#
Десериализация в произвольный класс с предварительным применением декларативной информации
var serializer = new XyloCode.BusinessData.Serializer();
serializer.DefaultCharacterEncoding = Encoding.Latin1;
serializer.AddBusinessObjectConfiguration(conf2);
ExtraCityPass2 newObj = serializer.Decode<ExtraCityPass2>(data);
#
Декодирование сериализованных данных в случае, если структура объекта неизвестна
var serializer = new XyloCode.BusinessData.Serializer();
serializer.DefaultCharacterEncoding = Encoding.Latin1;
string verbose = serializer.Print(data);
Console.WriteLine(verbose);
> BusinessData v1
> ********************************************
> (BusinessObjectId = 0)
> [1] => (Int32Var) 7714377
> [2] => (UTF8) USA
> [3] => (StringNumber) 2571459
> [4] => (String) Elvis Aaron Presley
> [5] => (Enum : Byte) 1
> [6] => (DateOnly) 08.01.1935
> [7] => (String) 3764 Elvis Presley Boulevard, Memphis, TN 38116
> [8] => (StringNumber) 19013323322
> [9] => (String) elvis@example.com
> [10] => (BooleanFalse) False
> [12] => (DateTimeOffset) 16.08.1977 0:00:00 -05:00
> ********************************************
#
Специализированные кодировки строк
Бинарный сериализатор позволяет использовать специализированные кодировки (производные классы от абстрактного System.Text.Encoding
) для преобразования данных публичных свойств типа string
, что особенно актуально для языков, алфавиты которых не покрываются ASCII-символами и содержат до 40..45 знаков. Кроме того, использование специализированных кодировок может рассматриваться как средство защиты информации.
Способ кодирования строки может быть задан непосредственно для конкретного публичного строкового свойства с помощью параметра mode
:
- атрибута аннотации данных
StringEncodingRuleAttribute
; - метода настройки свойства
SetStringEncodingRule()
Fluent API.
Параметр mode
является перечислением типа StringEncodingMode
и может принимать следующие значения:
UTF8
— использовать кодировку UTF-8;UTF16LE
— использовать кодировку UTF-16 с обратным порядком байтов;CodePage
— использовать кодировку с кодом, указанным в именованом аргументеCodePage
атрибута аннотации данных или метода настройки свойства Fluent API;Numeric
— использовать 4-х битную кодировку с поддержкой символов:1
,2
,3
,4
,5
,6
,7
,8
,9
,0
,*
,-
,.
,/
,:
, ' ' (пробел);Predefined
— использовать кодировку, экземпляр класса которой назначен свойствуDefaultCharacterEncoding
бинарного сериализатора.
#
Явное указание кода кодировки
При режиме кодирования StringEncodingMode.CodePage
сериализатор добавляет код используемой кодировки к каждому строковому свойству, которое кодируется этим способом. В случае, если бизнес-объект содержит множество таких свойств, возможно, имеет смысл рассмотреть способ использования с использованием предопределенной кодировки.
Пример настройки свойства методом аннотации данных с помощью атрибутов:
[StringEncodingRule(StringEncodingMode.CodePage, CodePage = 866)]
public string FullName { get; set; }
Пример настройки свойства средствами Fluent API:
conf.GetProperty("FullName")
.SetStringEncodingRule(StringEncodingMode.CodePage, CodePage = 866);
#
Использование предопределенной кодировки
Этот способ может являться наиболее экономичным с точки зрения размера сериализованного объекта. В то же время, декодировать строковые данные, сохраненные этим способом, без наличия библиотеки с необходимой кодировкой будет весьма затруднительно.
Кодирование и декодирование свойств строкового типа в режиме StringEncodingMode.Predefined
предполагает, что сериализатору уже известен способ кодирования строк в этом режиме, причём он является единственным, например:
var serializer = new XyloCode.BusinessData.Serializer();
serializer.DefaultCharacterEncoding = new RussianSixBitEncoding();
Настройка свойства методом аннотации данных с помощью атрибутов:
[StringEncodingRule(StringEncodingMode.Predefined)]
public string FullName { get; set; }
Настройка свойства средствами Fluent API:
conf.GetProperty("FullName")
.SetStringEncodingRule(StringEncodingMode.Predefined);