反序列化具有不同类型的GenericObject <t>的列表

时间:2019-04-06 03:58:09

标签: c# xml xml-serialization

我的最终目标是存储一些用于生成用于报告的SQL的参数,以尝试减少(或删除?)SQL注入的可能性。

因此,在我的解决方案中,我计划在应用程序的管理面板中创建报告定义。报表定义是用于生成输出的原始SQL,然后有一个参数定义要在UI中呈现给用户,并尝试保持强类型化。

参数是我当前卡住的位置。我可以正确地序列化参数列表,并且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 个答案:

答案 0 :(得分:1)

不幸的是,我为您提供了解决方案,但与您的期望不符。我不知道您是否尝试过此方法,但我希望这至少会对您有所帮助。

如果我错了,请纠正我,但是除非您推出自己的反序列化器,否则似乎无法为您要完成的工作提供支持。

可能缺少的是一种通知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 Injection”要求的Generic类型。但您仍然会遇到字符串的问题。 您需要确保转义任何字符,这将使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))
        }
    });

现在,您不再需要ReportParameterOfInt32ReportParameterOfDateTime的不同类类型。

对于要反序列化XML,如果要存储任何报表设计器信息,则可能需要在Type中添加一个单独的ReportParameter字段。但是,我不清楚您的要求是什么,但是它可能会在运行时帮助您以更简单的方式确定Type属性的Value

更新

@Patrick B发现可以使用XmlArrayItem指定ReportParameter的通用类型版本来完成此任务。有道理。

示例:

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>();
}