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

Deserializing a list of GenericObject<T> with varying types

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

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

参数是我目前卡住的地方。我可以正确地序列化参数列表并且 XML 看起来像我期望的那样,但尝试反序列化结果会出现一些异常。

我试图构建一个简单的方法来将 XML 字符串转换为一个对象并得到了一些 yuk。我明白异常和原因,就是不知道怎么解决。

当我尝试反序列化一个通用对象时,它失败了。在 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(或类似)

这就是我必须serialize/deserialize我在说什么。

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

然后我有一个扩展方法来序列化数据(最终将写入 table)

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

在编写了一个测试方法来进行简单的反序列化之后,我得到了一个可爱的异常:

Message: Test method X.Tests.TestSerialize.SerializeGeneric threw exception: System.InvalidOperationException: There is an error in XML document (1, 170). ---> System.InvalidOperationException: The specified type was not recognized: name='ReportParameterOfInt32', namespace='', at .

如有任何帮助,我们将不胜感激。

很遗憾,我有适合您的解决方案,但不符合您的期望。我不知道你是否尝试过这个,但我希望这至少能对你有所帮助。

如果我错了请纠正我,但除非您推出自己的反序列化器,否则似乎没有对您要完成的工作的支持。

可能缺少一种方法来通知 Xml 序列化程序使用通用 class ReportParameter<T> 以及它应该考虑哪些类型的 T

因此,我没有使用通用参数方法,而是显式地手动滚动了那些 classes。

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" 要求,但您仍然会遇到 strings 的问题。 您需要确保您 转义任何字符 ,这将使 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))
        }
    });

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

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

更新

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