Uri.EscapeDataString() - 无效的URI:URI字符串太长。

30

我在使用紧凑框架/ C#在Windows Mobile上。

在我的应用程序中,我通过序列化对象并使用HttpWebRequest / POST请求将信息发送到服务器来上传数据。 在服务器上,发布数据被反序列化并保存到数据库中。

前几天,我意识到发布数据中存在特殊字符(和等等)。 因此,我将Uri.EscapeDataString()引入该方法,然后一切正常。

但是,今天我发现当应用程序尝试上传大量数据时存在问题(我目前不确定什么情况下会被认为是“大量”!)

现有代码(有点)

var uploadData = new List<Things>();

uploadData.Add(new Thing() { Name = "Test 01" });
uploadData.Add(new Thing() { Name = "Test 02" });
uploadData.Add(new Thing() { Name = "Test with an & Ampersand " }); // Do this a lot!!

var postData = "uploadData=" + Uri.EscapeDataString(JsonConvert.SerializeObject(uploadData, new IsoDateTimeConverter()));

问题

调用Uri.EscapeDataString()方法会导致以下异常:

System.UriFormatException: 无效的URI: URI字符串太长。

问题是:

是否有其他方法来准备要上传的数据?

据我所知,紧缩框架中没有HttpUtility(它有自己的编码/解码方法)。


1
你可以编写自己的实现吗?EscapeDataString() 似乎主要是为了方便...基于需要转义的字符库来执行普通的 String.Replace - Smudge202
Msdn指出:UriFormatException- stringToEscape的长度超过32766个字符。 - fluent
正如Smudge202所建议的那样,我只是编写了自己的实现。 - ETFairfax
1
这个实现方案发一下怎么样? - Oleg Grishko
我本来想贴出实现代码的,但它有点糟糕!最近我改用了被接受的答案。 - ETFairfax
显示剩余3条评论
6个回答

38
或者你可以简单地分割你的字符串并对每个块调用Uri.EscapeDataString(string),以避免重新实现该函数。 示例代码:
        String value = "large string to encode";
        int limit = 2000;

        StringBuilder sb = new StringBuilder();
        int loops = value.Length / limit;

        for (int i = 0; i <= loops; i++)
        {
            if (i < loops)
            {
                sb.Append(Uri.EscapeDataString(value.Substring(limit * i, limit)));
            }
            else
            {
                sb.Append(Uri.EscapeDataString(value.Substring(limit * i)));
            }
        }

5
在.NET 4.5中,EscapeDataString的限制为65520个字符,因此可以利用这一点来减少所需的迭代次数。 - Knaģis
很好。Uri.Unescape有这种问题吗?看起来似乎没有,但我还是想确认一下。 - Valentin Kuzub
@Knagis 我不确定你为什么在这里提到迭代次数,因为它几乎不能占用执行时间的大部分。使用值的长度初始化 StringBuilder 明显听起来更有助于性能提升。 - Valentin Kuzub
3
更新一下:.NET 4.5 中 EscapeDataString 的正确当前限制是32766个字符(不是@Knagi上面提到的65520个字符):https://msdn.microsoft.com/zh-cn/library/system.uri.escapedatastring%28v=vs.110%29.aspx - Nick
3
如果你实际尝试一下,可能会发现65520是实际限制(不包括在内,所以最多只能使用65519),与文档中所说的不同。 - Jon Hanna

5

"Alberto de Paola"的回答很好。

然而,要取消转义的数据会有一点棘手,因为你必须避免在编码字符的中间截断编码字符串(否则将破坏原始字符串的完整性)。

以下是我解决此问题的方法:

public static string EncodeString(string str)
{
    //maxLengthAllowed .NET < 4.5 = 32765;
    //maxLengthAllowed .NET >= 4.5 = 65519;
    int maxLengthAllowed = 65519;
    StringBuilder sb = new StringBuilder();
    int loops = str.Length / maxLengthAllowed;

    for (int i = 0; i <= loops; i++)
    {
        sb.Append(Uri.EscapeDataString(i < loops
            ? str.Substring(maxLengthAllowed * i, maxLengthAllowed)
            : str.Substring(maxLengthAllowed * i)));
    }

    return sb.ToString();
}

public static string DecodeString(string encodedString)
{
    //maxLengthAllowed .NET < 4.5 = 32765;
    //maxLengthAllowed .NET >= 4.5 = 65519;
    int maxLengthAllowed = 65519;

    int charsProcessed = 0;
    StringBuilder sb = new StringBuilder();

    while (encodedString.Length > charsProcessed)
    {
        var stringToUnescape = encodedString.Substring(charsProcessed).Length > maxLengthAllowed
            ? encodedString.Substring(charsProcessed, maxLengthAllowed)
            : encodedString.Substring(charsProcessed);

        // If the loop cut an encoded tag (%xx), we cut before the encoded char to not loose the entire char for decoding
        var incorrectStrPos = stringToUnescape.Length == maxLengthAllowed ? stringToUnescape.IndexOf("%", stringToUnescape.Length - 4, StringComparison.InvariantCulture) : -1;
        if (incorrectStrPos > -1)
        {
            stringToUnescape = encodedString.Substring(charsProcessed).Length > incorrectStrPos
                ? encodedString.Substring(charsProcessed, incorrectStrPos)
                : encodedString.Substring(charsProcessed);
        }

        sb.Append(Uri.UnescapeDataString(stringToUnescape));
        charsProcessed += stringToUnescape.Length;
    }

    var decodedString = sb.ToString();

    // ensure the string is sanitized here or throw exception if XSS / SQL Injection is found
    SQLHelper.SecureString(decodedString);
    return decodedString;
}

测试这些功能:

var testString = "long string to encode";
var encodedString = EncodeString(testString);
var decodedString = DecodeString(encodedString);

Console.WriteLine(decodedString == testString ? "integrity respected" : "integrity broken");

希望这可以帮助避免一些麻烦 ;)

这将构建一个更好的整体解决方案。我在要翻译的字符中间被分割所困扰。 - user3841460

2
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < originalString.Length; i++)
{
    if ((originalString[i] >= 'a' && originalString[i] <= 'z') || 
        (originalString[i] >= 'A' && originalString[i] <= 'Z') || 
        (originalString[i] >= '0' && originalString[i] <= '9'))
    {
        stringBuilder.Append(originalString[i]);
    }
    else
    {
        stringBuilder.AppendFormat("%{0:X2}", (int)originalString[i]);
    }
}

string result = stringBuilder.ToString();

1

我一直在使用System.Web.HttpUtility.UrlEncode,似乎更好地处理了较长的字符串。


0

使用 System.Web.HttpUtility.UrlEncode(基于此答案):

        value = HttpUtility.UrlEncode(value)
            .Replace("!", "%21")
            .Replace("(", "%28")
            .Replace(")", "%29")
            .Replace("*", "%2A")
            .Replace("%7E", "~"); // undo escape

1
百分之几怎么样? - luiseduardohd
链接的答案包括: WebUtility.UrlEncode 将空格编码为 +; Uri.EscapeDataString 将其编码为 %20。 因此,我们不应该加上 .Replace("+", "%20") 吗? - Paul B.

0

我需要另一种解决方案,因为Pouki的解决方案在处理西里尔字母和符号时无法正常工作。

替代方案如下:

    protected const int MaxLengthAllowed = 32765;
    private static string UnescapeString(string encodedString)
    {
        var charsProccessed = 0;

        var sb = new StringBuilder();

        while (encodedString.Length > charsProccessed)
        {
            var isLastIteration = encodedString.Substring(charsProccessed).Length < MaxLengthAllowed;

            var stringToUnescape = isLastIteration
                ? encodedString.Substring(charsProccessed)
                : encodedString.Substring(charsProccessed, MaxLengthAllowed);

            while (!Uri.IsWellFormedUriString(stringToUnescape, UriKind.RelativeOrAbsolute) || stringToUnescape.Length == 0)
            {
                stringToUnescape = stringToUnescape.Substring(0, stringToUnescape.Length - 1);
            }

            sb.Append(Uri.UnescapeDataString(stringToUnescape));
            charsProccessed += stringToUnescape.Length;
        }

        return sb.ToString();
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接