Десериализация списка GenericObject <T>с различными типами - PullRequest
1 голос
/ 06 апреля 2019

Моя конечная цель - сохранить некоторые параметры, используемые для генерации некоторого SQL, используемого для создания отчетов, с целью уменьшить (или удалить?) Возможность внедрения SQL.

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

Параметры находятся там, где я сейчас нахожусьзастрял.Я могу правильно сериализовать список параметров, и XML выглядит так, как я ожидал, но пытаясь десериализовать результаты в некоторых исключениях.

Я попытался создать простой метод для преобразования строки XML в объект и получил некоторыегогот.Я понимаю исключение и причину, я просто не знаю, как решить.

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

<?xml version="1.0" encoding="utf-16"?>
<ReportParameters xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Parameters>
        <ReportParameter xsi:type="ReportParameterOfInt32">
            <Name>Test</Name>
            <Value xsi:type="xsd:int">4</Value>
        </ReportParameter>
        <ReportParameter xsi:type="ReportParameterOfDateTime">
            <Name>Startdate</Name>
            <Value xsi:type="xsd:dateTime">2019-04-05T22:52:25.9869948-05:00</Value>
        </ReportParameter>
    </Parameters>
</ReportParameters>

Тип в ReportParameter, я думаю, является моим зависанием.xsi: type = "ReportParameterOfInt32"

Должен разрешить в ReportParameter (или аналогичный)

Вот что мне нужно для сериализации / десериализации того, о чем я говорю.

public class ReportParameter
    {
        /// <summary>
        /// All names are prefixed automatically with the @ sign.
        /// This is the name that will appear in the query when building/writing
        /// </summary>
        public string Name { get; set; }
        public object Value { get; set; }
    }

    /// <summary>
    /// A generic object type that can hold many data types
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ReportParameter<T> : ReportParameter
    {

        T _value;

        [XmlIgnore]
        public new T Value
        {
            get { return _value; }
            set
            {
                base.Value = value;
                _value = value;
            }
        }
    }

Затем у меня есть метод расширения для сериализации данных (который в конечном итоге будет записан в таблицу)

public static string Serialize(this ReportParameters parameters)
        {
            List<Type> types = new List<Type>();
            // Loop over all the parameters
            foreach (var a in parameters.Parameters)
            {
                // See if this Type is already in the list of potential types
                if (types.Contains(a.GetType()))
                {
                    continue;
                }
                // Add it to the List
                types.Add(a.GetType());
            }
            types.Add(typeof(ReportParameters));
            Type[] genericTypes = types.ToArray();

            XmlSerializer xsSubmit = new XmlSerializer(typeof(ReportParameters), genericTypes);
            string xml = string.Empty;

            using (var sww = new StringWriter())
            {
                using (var writer = XmlWriter.Create(sww))
                {
                    xsSubmit.Serialize(writer, parameters);
                    xml = sww.ToString();
                    return xml;
                }
            }
        }

После написания тестового метода для простой десериализации я получаю прекрасное исключение:

Сообщение: метод теста X.Tests.TestSerialize.SerializeGeneric выбросил исключение: System.InvalidOperationException: в документе XML есть ошибка (1, 170).---> System.InvalidOperationException: указанный тип не был распознан: name = 'ReportParameterOfInt32', namespace = '', at.

Любая помощь здесь будет принята с благодарностью.

1 Ответ

1 голос
/ 06 апреля 2019

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

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

Что может отсутствовать, так это способ информирования сериализатора Xml об использовании универсального класса ReportParameter<T> и о том, какие типы T он должен учитывать.

Таким образом, вместо того, чтобы использовать подход «Общие параметры», я вручную прокрутил эти классы.

public class ReportParameterOfInt32 : ReportParameter
{
    int _value;

    [XmlIgnore]
    public new int Value
    {
        get { return _value; }
        set
        {
            base.Value = value;
            _value = value;
        }
    }
}

public class ReportParameterOfDateTime : ReportParameter
{
    DateTime _value;

    [XmlIgnore]
    public new DateTime Value
    {
        get { return _value; }
        set
        {
            base.Value = value;
            _value = value;
        }
    }
}

Обновлен ReportParameters (который я догадался, как он выглядит, поскольку он не был указан в вашем вопросе):

public class ReportParameters
{
    [XmlArrayItem(typeof(ReportParameterOfInt32))]
    [XmlArrayItem(typeof(ReportParameterOfDateTime))]
    public ReportParameter[] Parameters { get; set; }
}

Упростил сериализатор и написал десериализатор:

private static string Serialize(ReportParameters parameters)
{
    XmlSerializer xsSubmit = new XmlSerializer(typeof(ReportParameters));
    string xml = string.Empty;

    using (var sww = new StringWriter())
    {
        using (var writer = XmlWriter.Create(sww))
        {
            xsSubmit.Serialize(writer, parameters);
            xml = sww.ToString();
            return xml;
        }
    }
}

private static ReportParameters Deserialize(string xml)
{
    XmlSerializer xsSubmit = new XmlSerializer(typeof(ReportParameters));
    using (var reader = new StringReader(xml))
    {
        return (ReportParameters)xsSubmit.Deserialize(reader);
    }
}

Написал тестовый код, который затем выполнялся, как и ожидалось:

var xml = Serialize(
    new ReportParameters
    {
        Parameters = new ReportParameter[]
        {
            new ReportParameterOfInt32 { Name = "Test", Value = 4 },
            new ReportParameterOfDateTime { Name = "Startdate", Value = new DateTime(2019, 04, 05, 22, 52, 25) }
        }
    });
var obj = Deserialize(xml);

Производство:

<?xml version="1.0" encoding="utf-16"?>
<ReportParameters xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <Parameters>
        <ReportParameterOfInt32>
            <Name>Test</Name>
            <Value xsi:type="xsd:int">4</Value>
        </ReportParameterOfInt32>
        <ReportParameterOfDateTime>
            <Name>Startdate</Name>
            <Value xsi:type="xsd:dateTime">2019-04-05T22:52:25</Value>
        </ReportParameterOfDateTime>
    </Parameters>
</ReportParameters>

Я знаю, что вы можете чувствовать себя немного разочарованным, однако я думаю, что вы можете просто использовать оболочки, чтобы гарантировать правильную запись в базовый тип без необходимости сериализации универсального типа, который, как мы надеемся, удовлетворяет вашему требованию «SQL-инъекции» но у вас все еще будет проблема с строк . Вам необходимо убедиться, что вы экранировали все символы , которые сделают возможным внедрение SQL, поскольку строки будут по-прежнему уязвимы для этого при создании оператора SQL для отправки в БД. Я уверен, что вы можете Google, как это сделать (так как я думаю, что это совершенно другое обсуждение вашего вопроса).

Таким образом, вместо обновления нового ReportParameter вы можете иметь:

private ReportParameter GetReportParameter<T>(string name, T value)
{
    return new ReportParameter
    {
         Name = name,
         Value = value
    };
}

Затем при сериализации:

var xml = Serialize(
    new ReportParameters
    {
        Parameters = new ReportParameter[]
        {
            GetReportParameter("Test", 4),
            GetReportParameter("Startdate", new DateTime(2019, 04, 05, 22, 52, 25))
        }
    });

Теперь вы устраняете необходимость в различных типах классов ReportParameterOfInt32 и ReportParameterOfDateTime.

Что касается десериализации XML, вам может понадобиться добавить отдельное поле Type в ваш ReportParameter, если вы собираетесь хранить какую-либо информацию о дизайнере отчетов. Тем не менее, я не знаю точно, каковы ваши требования, но это может помочь вам во время выполнения определить Type свойства Value более простым способом.

UPDATE

@ Патрик Б обнаружил, что можно использовать XmlArrayItem для указания версии Generic Type ReportParameter для достижения этой цели. Имеет смысл.

* +1054 * Пример:
public class ReportParameters 
{ 
    [XmlArrayItem(Type = typeof(ReportParameter<int>))] 
    [XmlArrayItem(Type = typeof(ReportParameter<int?>))] 
    [XmlArrayItem(Type = typeof(ReportParameter<DateTime>))] 
    public List<ReportParameter> Parameters { get; set; } = new List<ReportParameter>();
}
...