反序列化GenericObject列表 各种类型

问题描述 投票:1回答:1

我的最终目标是存储一些用于生成一些用于报告的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(或类似)

这是我必须序列化/反序列化我正在谈论的内容。

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。

这里的任何帮助将不胜感激。

c# xml xml-serialization
1个回答
1
投票

我有一个解决方案,但不幸的是,这不符合您的期望。我不知道你是否试过这个,但我希望这至少对你有帮助。

如果我错了,请纠正我,但似乎并不支持你想要完成的任务,除非你推出自己的反序列化器。

可能缺少的是一种通知Xml Serializer使用Generic类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; }
}

简化了Serializer并编写了Deserializer:

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>

我知道你可能会感到有点失望,但是,我确实认为你可以使用包装器来确保它正确地写入底层类型而不必序列化Generic类型,它应该有希望满足你的“SQL注入”要求但是你仍然会有字符串问题。您需要确保转义任何可以使SQL注入成为可能的字符,因为在构建要发送到DB的SQL语句时,字符串仍然容易受到攻击。我相信你可以谷歌如何做到这一点(因为我认为这是一个完全不同的讨论你的问题)。

因此,您可以拥有以下内容,而不是新建一个新的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

UPDATE

@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>();
}
© www.soinside.com 2019 - 2024. All rights reserved.