Как правильно десериализовать XML с несколькими пространствами имен в C#
Я пытаюсь десериализовать XML-ответ от API, который содержит несколько пространств имен. Структура XML-данных выглядит следующим образом:
<ns1:alerts xmlns:ns1="http://gov.fema.ipaws.services/feed">
<alert xmlns="urn:oasis:names:tc:emergency:cap:1.2">
<identifier>All-Channels-Test-Message-RT_Automation_124_789f95e3-e02f-4e61-8121
Мой текущий код C# для десериализации выглядит следующим образом:
var serializer = new XmlSerializer(
typeof(List<Alert>),
new XmlRootAttribute("alerts") { Namespace = "http://gov.fema.ipaws.services/feed" });
var alerts = serializer.Deserialize(stream) as Alert[];
В моем классе Alert я установил атрибуты с пространством имен и корневым элементом:
[XmlRoot("alert", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public class Alert
Когда я десериализую XML, я получаю 0 экземпляров, хотя в ответе содержится 1-2 предупреждения в зависимости от ленты API. Я проверил, что ответ отформатирован правильно. Моя догадка заключается в том, что я десериализую неправильно из-за нескольких пространств имен. Как правильно десериализовать XML с несколькими пространствами имен в C#?
Десериализация XML с несколькими пространствами имен в C# требует тщательной настройки XmlSerializer для правильной обработки сложной структуры пространств имен. Ваша текущая проблема, скорее всего, связана с несоответствующими объявлениями пространств имен между корневым и вложенными элементами.
Чтобы правильно десериализовать XML с несколькими пространствами имен, такими как формат CAP (Common Alerting Protocol), с которым вы работаете, вам необходимо настроить атрибуты сериализации так, чтобы они точно соответствовали структуре XML.
Содержание
- Понимание структуры пространств имен
- Решение 1: Правильная настройка корневого элемента
- Решение 2: Использование атрибутов XmlElement
- Решение 3: Пользовательская обработка пространств имен
- Решение 4: Расширенная поддержка нескольких пространств имен
- Полный пример реализации
- Лучшие практики и устранение неполадок
Понимание структуры пространств имен
Ваш XML содержит два различных пространства имен:
http://gov.fema.ipaws.services/feed- для корневого элементаalertsurn:oasis:names:tc:emergency:cap:1.2- для элементовalertвнутри
XmlSerializer должен понимать эту иерархическую структуру пространств имен, чтобы правильно сопоставлять XML-элементы с вашими объектами C#.
Решение 1: Правильная настройка корневого элемента
Основная проблема заключается в том, что вы пытаетесь десериализовать List<Alert> напрямую, когда ваша структура XML имеет корневой элемент, содержащий несколько элементов alert. Вам нужны два класса:
[XmlRoot("alerts", Namespace = "http://gov.fema.ipaws.services/feed")]
public class AlertsContainer
{
[XmlElement("alert", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public List<Alert> Alerts { get; set; }
}
[XmlRoot("alert", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public class Alert
{
// Ваши свойства alert здесь
public string identifier { get; set; }
// Добавьте другие свойства CAP alert при необходимости
}
Затем десериализуйте следующим образом:
var serializer = new XmlSerializer(typeof(AlertsContainer));
var container = (AlertsContainer)serializer.Deserialize(stream);
var alerts = container.Alerts;
Решение 2: Использование атрибутов XmlElement
Для более сложных XML-структур с несколькими пространствами имен вы можете указывать пространства имен непосредственно в атрибутах XmlElement:
public class Alert
{
[XmlElement("identifier", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Identifier { get; set; }
[XmlElement("sender", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Sender { get; set; }
[XmlElement("sent", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public DateTime Sent { get; set; }
// Добавьте другие элементы CAP с их соответствующими пространствами имен
}
Решение 3: Пользовательская обработка пространств имен
Если вам нужно обрабатывать XML, в котором пространства имен могут меняться или быть непредсказуемыми, вы можете создать пользовательский XmlTextReader, который игнорирует пространства имен при десериализации:
public class IgnoreNamespaceXmlTextReader : XmlTextReader
{
public IgnoreNamespaceXmlTextReader(TextReader reader) : base(reader) { }
public override string NamespaceURI => string.Empty;
public override string LocalName =>
base.LocalName.Contains(":") ?
base.LocalName.Substring(base.LocalName.IndexOf(":") + 1) :
base.LocalName;
}
Затем используйте его следующим образом:
var serializer = new XmlSerializer(typeof(AlertsContainer));
using (var stringReader = new StreamReader(stream))
using (var xmlReader = new IgnoreNamespaceXmlTextReader(stringReader))
{
var container = (AlertsContainer)serializer.Deserialize(xmlReader);
}
Решение 4: Расширенная поддержка нескольких пространств имен
Для обработки нескольких версий схем или сложных сценариев с пространствами имен используйте XmlSerializerNamespaces:
// Для десериализации с несколькими версиями пространств имен
var serializer = new XmlSerializer(typeof(AlertsContainer),
new XmlRootAttribute { ElementName = "alerts", Namespace = "http://gov.fema.ipaws.services/feed" });
// Для сериализации с несколькими префиксами пространств имен
var namespaces = new XmlSerializerNamespaces();
namespaces.Add("ns1", "http://gov.fema.ipaws.services/feed");
namespaces.Add("cap", "urn:oasis:names:tc:emergency:cap:1.2");
Полный пример реализации
Вот полный рабочий пример для вашего сценария CAP alert:
using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
public class CAPAlertDeserializer
{
public List<Alert> DeserializeAlerts(Stream xmlStream)
{
var serializer = new XmlSerializer(typeof(AlertsContainer));
using (var reader = new StreamReader(xmlStream))
{
var container = (AlertsContainer)serializer.Deserialize(reader);
return container.Alerts;
}
}
}
[XmlRoot("alerts", Namespace = "http://gov.fema.ipaws.services/feed")]
public class AlertsContainer
{
[XmlElement("alert", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public List<Alert> Alerts { get; set; }
}
[XmlRoot("alert", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public class Alert
{
[XmlElement("identifier", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Identifier { get; set; }
[XmlElement("sender", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Sender { get; set; }
[XmlElement("sent", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public DateTime Sent { get; set; }
[XmlElement("status", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Status { get; set; }
[XmlElement("msgType", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string MessageType { get; set; }
[XmlElement("scope", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Scope { get; set; }
[XmlElement("info", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public AlertInfo Info { get; set; }
}
[XmlRoot("info", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public class AlertInfo
{
[XmlElement("language", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Language { get; set; }
[XmlElement("category", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Category { get; set; }
[XmlElement("event", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Event { get; set; }
[XmlElement("responseType", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string ResponseType { get; set; }
[XmlElement("urgency", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Urgency { get; set; }
[XmlElement("severity", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Severity { get; set; }
[XmlElement("certainty", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Certainty { get; set; }
[XmlElement("audience", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Audience { get; set; }
[XmlElement("eventCode", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public List<EventCode> EventCodes { get; set; }
[XmlElement("effective", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public DateTime Effective { get; set; }
[XmlElement("expires", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public DateTime? Expires { get; set; }
[XmlElement("onset", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public DateTime? Onset { get; set; }
[XmlElement("senderName", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string SenderName { get; set; }
[XmlElement("headline", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Headline { get; set; }
[XmlElement("description", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Description { get; set; }
[XmlElement("instruction", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Instruction { get; set; }
[XmlElement("web", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Web { get; set; }
[XmlElement("contact", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Contact { get; set; }
[XmlElement("parameter", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public List<Parameter> Parameters { get; set; }
[XmlElement("area", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public List<Area> Areas { get; set; }
}
[XmlRoot("eventCode", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public class EventCode
{
[XmlElement("valueName", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string ValueName { get; set; }
[XmlElement("value", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Value { get; set; }
}
[XmlRoot("parameter", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public class Parameter
{
[XmlElement("valueName", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string ValueName { get; set; }
[XmlElement("value", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Value { get; set; }
}
[XmlRoot("area", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public class Area
{
[XmlElement("areaDesc", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string AreaDescription { get; set; }
[XmlElement("circle", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public Circle Circle { get; set; }
[XmlElement("polygon", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public string Polygon { get; set; }
[XmlElement("geocode", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public List<Geocode> Geocodes { get; set; }
}
[XmlRoot("circle", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public class Circle
{
[XmlAttribute("radius")]
public string Radius { get; set; }
[XmlAttribute("latitude")]
public string Latitude { get; set; }
[XmlAttribute("longitude")]
public string Longitude { get; set; }
}
[XmlRoot("geocode", Namespace = "urn:oasis:names:tc:emergency:cap:1.2")]
public class Geocode
{
[XmlAttribute("valueName")]
public string ValueName { get; set; }
[XmlText]
public string Value { get; set; }
}
Лучшие практики и устранение неполадок
При работе с несколькими пространствами имен в десериализации XML учитывайте следующие лучшие практики:
-
Всегда точно соответствуйте структуре XML - Убедитесь, что ваши классы C# точно отражают иерархию XML и объявления пространств имен.
-
Используйте конкретные объявления пространств имен в атрибутах XmlElement и XmlRoot, а не полагайтесь на значения по умолчанию.
-
Правильно обрабатывайте префиксы пространств имен - XmlSerializer работает с URI пространств имен, а не с префиксами.
-
Тестируйте с примерами XML - Всегда десериализуйте фактические XML-ответы, чтобы убедиться, что ваша структура классов соответствует.
-
Добавляйте объявления XML пространств имен в ваши классы с помощью атрибута XmlNamespaceDeclarations, если вам нужно сохранять информацию о пространствах имен во время сериализации.
-
Используйте XmlSerializerNamespaces, когда вам нужно контролировать префиксы пространств имен в выходном XML.
-
Рассмотрите возможность использования XML-схем - Инструменты вроде xsd.exe могут генерировать классы C# из XML-схем, обеспечивая правильную обработку пространств имен.
Если у вас продолжают возникать проблемы, попробуйте сначала десериализовать в универсальную XML-структуру, чтобы изучить фактическую структуру XML, а затем сопоставить ее с вашими конкретными классами:
var doc = new XmlDocument();
doc.Load(stream);
Console.WriteLine(doc.OuterXml); // Изучите фактическую структуру XML
Источники
- Десериализация XML с несколькими пространствами имен в C# - Stack Overflow
- Десериализация XML с пространством имен в .NET core | GoTask
- Игнорирование пространств имен в XML при десериализации в C# | Brian Pedersen’s Sitecore and .NET Blog
- Десериализация XML с пространством имен и несколькими вложенными элементами - Stack Overflow
- Использование нескольких пространств имен при десериализации с .NET XmlSerializer - Stack Overflow
- Проблема с пространством имен в XmlSerializer - Microsoft Q&A
Заключение
Правильная десериализация XML с несколькими пространствами имен в C# требует тщательной настройки атрибутов XmlSerializer для точного соответствия структуре XML. Ключевые решения:
-
Создавайте правильные классы-контейнеры, которые соответствуют иерархии XML, включая как корневой элемент, так и вложенные элементы с их соответствующими пространствами имен.
-
Используйте атрибуты XmlElement с явными объявлениями пространств имен для каждого XML-элемента, который принадлежит другому пространству имен.
-
Рассмотрите техники пользовательской обработки пространств имен, такие как IgnoreNamespaceXmlTextReader, для непредсказуемых XML-структур.
-
Реализуйте полные классы CAP alert с правильным сопоставлением пространств имен для всех элементов в протоколе экстренных оповещений.
Следуя этим подходам и обеспечивая точное соответствие ваших классов C# структуре XML с правильными объявлениями пространств имен, вы успешно десериализуете сложные XML-документы с несколькими пространствами имен в C#.