我正在使用Azure存储表,我有数据进入RowKey,其中包含斜杠。根据{{3}},PartitionKey和RowKey中不允许使用以下字符:
正斜杠(/)字符
反斜杠()字符
数字符号(#)字符
问号(?)字符
控制字符从U + 0000到U + 001F,包括:
水平制表符(\ t)字符
换行符(\ n)字符
回车(\ r)字符
控制字符从U + 007F到U + 009F
我见过有些人使用URL编码来解决这个问题。不幸的是,这可能会产生一些问题,例如能够插入但无法删除某些实体。我也看到有些人使用base64编码,但是这也可以包含不允许的字符。
如何在不运行不允许的字符或滚动自己的编码的情况下有效编码RowKey?
答案 0 :(得分:12)
当URL为Base64编码时,Azure表存储键列中唯一无效的字符是正斜杠(' /')。要解决此问题,只需将正斜杠字符替换为另一个字符,该字符在(1)Azure表存储密钥列中有效,(2)不是Base64字符。我发现的最常见的例子(在其他答案中引用)是用下划线(' _')替换正斜杠(' /')。
private static String EncodeUrlInKey(String url)
{
var keyBytes = System.Text.Encoding.UTF8.GetBytes(url);
var base64 = System.Convert.ToBase64String(keyBytes);
return base64.Replace('/','_');
}
解码时,只需撤消替换的字符(首先!),然后Base64解码生成的字符串。这就是它的全部内容。
private static String DecodeUrlInKey(String encodedKey)
{
var base64 = encodedKey.Replace('_', '/');
byte[] bytes = System.Convert.FromBase64String(base64);
return System.Text.Encoding.UTF8.GetString(bytes);
}
有人建议其他Base64字符也需要编码。根据{{3}},事实并非如此。
答案 1 :(得分:9)
我遇到了同样的需要。
我对Base64编码不满意,因为它将一个人类可读的字符串变成一个无法识别的字符串,并且无论是否符合规则,都会膨胀字符串的大小(绝大多数字符都是丢失的)不是需要转义的非法字符。)
这是一个编码器/解码器使用'!'作为一个转义字符,就像传统上使用反斜杠字符一样。
public static class TableKeyEncoding
{
// https://msdn.microsoft.com/library/azure/dd179338.aspx
//
// The following characters are not allowed in values for the PartitionKey and RowKey properties:
// The forward slash(/) character
// The backslash(\) character
// The number sign(#) character
// The question mark (?) character
// Control characters from U+0000 to U+001F, including:
// The horizontal tab(\t) character
// The linefeed(\n) character
// The carriage return (\r) character
// Control characters from U+007F to U+009F
public static string Encode(string unsafeForUseAsAKey)
{
StringBuilder safe = new StringBuilder();
foreach (char c in unsafeForUseAsAKey)
{
switch (c)
{
case '/':
safe.Append("!f");
break;
case '\\':
safe.Append("!b");
break;
case '#':
safe.Append("!p");
break;
case '?':
safe.Append("!q");
break;
case '\t':
safe.Append("!t");
break;
case '\n':
safe.Append("!n");
break;
case '\r':
safe.Append("!r");
break;
case '!':
safe.Append("!!");
break;
default:
if (c <= 0x1f || (c >= 0x7f && c <= 0x9f))
{
int charCode = c;
safe.Append("!x" + charCode.ToString("x2"));
}
else
{
safe.Append(c);
}
break;
}
}
return safe.ToString();
}
public static string Decode(string key)
{
StringBuilder decoded = new StringBuilder();
int i = 0;
while (i < key.Length)
{
char c = key[i++];
if (c != '!' || i == key.Length)
{
// There's no escape character ('!'), or the escape should be ignored because it's the end of the array
decoded.Append(c);
}
else
{
char escapeCode = key[i++];
switch (escapeCode)
{
case 'f':
decoded.Append('/');
break;
case 'b':
decoded.Append('\\');
break;
case 'p':
decoded.Append('#');
break;
case 'q':
decoded.Append('?');
break;
case 't':
decoded.Append('\t');
break;
case 'n':
decoded.Append("\n");
break;
case 'r':
decoded.Append("\r");
break;
case '!':
decoded.Append('!');
break;
case 'x':
if (i + 2 <= key.Length)
{
string charCodeString = key.Substring(i, 2);
int charCode;
if (int.TryParse(charCodeString, NumberStyles.HexNumber, NumberFormatInfo.InvariantInfo, out charCode))
{
decoded.Append((char)charCode);
}
i += 2;
}
break;
default:
decoded.Append('!');
break;
}
}
}
return decoded.ToString();
}
}
由于在编写自己的编码器时应该格外小心,我也为它编写了一些单元测试。
using Xunit;
namespace xUnit_Tests
{
public class TableKeyEncodingTests
{
const char Unicode0X1A = (char) 0x1a;
public void RoundTripTest(string unencoded, string encoded)
{
Assert.Equal(encoded, TableKeyEncoding.Encode(unencoded));
Assert.Equal(unencoded, TableKeyEncoding.Decode(encoded));
}
[Fact]
public void RoundTrips()
{
RoundTripTest("!\n", "!!!n");
RoundTripTest("left" + Unicode0X1A + "right", "left!x1aright");
}
// The following characters are not allowed in values for the PartitionKey and RowKey properties:
// The forward slash(/) character
// The backslash(\) character
// The number sign(#) character
// The question mark (?) character
// Control characters from U+0000 to U+001F, including:
// The horizontal tab(\t) character
// The linefeed(\n) character
// The carriage return (\r) character
// Control characters from U+007F to U+009F
[Fact]
void EncodesAllForbiddenCharacters()
{
List<char> forbiddenCharacters = "\\/#?\t\n\r".ToCharArray().ToList();
forbiddenCharacters.AddRange(Enumerable.Range(0x00, 1+(0x1f-0x00)).Select(i => (char)i));
forbiddenCharacters.AddRange(Enumerable.Range(0x7f, 1+(0x9f-0x7f)).Select(i => (char)i));
string allForbiddenCharacters = String.Join("", forbiddenCharacters);
string allForbiddenCharactersEncoded = TableKeyEncoding.Encode(allForbiddenCharacters);
// Make sure decoding is same as encoding
Assert.Equal(allForbiddenCharacters, TableKeyEncoding.Decode(allForbiddenCharactersEncoded));
// Ensure encoding does not contain any forbidden characters
Assert.Equal(0, allForbiddenCharacters.Count( c => allForbiddenCharactersEncoded.Contains(c) ));
}
}
}
答案 2 :(得分:1)
看到这些链接 http://tools.ietf.org/html/rfc4648#page-7 Code for decoding/encoding a modified base64 URL(另见第二个回答:https://stackoverflow.com/a/1789179/1094268)
我自己遇到了这个问题。这些是我现在使用的功能。我在我提到的第二个答案中使用了这个技巧,并更改了与可能仍然出现的azure键不兼容的+
和/
。
private static String EncodeSafeBase64(String toEncode)
{
if (toEncode == null)
throw new ArgumentNullException("toEncode");
String base64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(toEncode));
StringBuilder safe = new StringBuilder();
foreach (Char c in base64String)
{
switch (c)
{
case '+':
safe.Append('-');
break;
case '/':
safe.Append('_');
break;
default:
safe.Append(c);
break;
}
}
return safe.ToString();
}
private static String DecodeSafeBase64(String toDecode)
{
if (toDecode == null)
throw new ArgumentNullException("toDecode");
StringBuilder deSafe = new StringBuilder();
foreach (Char c in toDecode)
{
switch (c)
{
case '-':
deSafe.Append('+');
break;
case '_':
deSafe.Append('/');
break;
default:
deSafe.Append(c);
break;
}
}
return Encoding.UTF8.GetString(Convert.FromBase64String(deSafe.ToString()));
}
答案 3 :(得分:1)
如果它只是斜杠,你可以简单地用另一个字符写入表中替换它们,比如说'|'并在阅读时重新替换它们。
答案 4 :(得分:1)
我所看到的是,尽管在技术上允许使用很多非字母数字字符,但它并不像分区和行键那样工作得非常好。
我看了已经在这里和其他地方给出的答案并写了这个: https://github.com/JohanNorberg/AlphaNumeric
两个字母数字编码器。
如果你需要转义一个主要是字母数字的字符串,你可以使用它:
AlphaNumeric.English.Encode(str);
如果你需要转义一个大部分不是字母数字的字符串,你可以使用它:
AlphaNumeric.Data.EncodeString(str);
编码数据:
var base64 = Convert.ToBase64String(bytes);
var alphaNumericEncodedString = base64
.Replace("0", "01")
.Replace("+", "02")
.Replace("/", "03")
.Replace("=", "04");
但是,如果你想使用例如电子邮件地址作为一个rowkey,你只想逃避'@'和'。'。这段代码就是这样做的:
char[] validChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ3456789".ToCharArray();
char[] allChars = rawString.ToCharArray();
StringBuilder builder = new StringBuilder(rawString.Length * 2);
for(int i = 0; i < allChars.Length; i++)
{
int c = allChars[i];
if((c >= 51 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122))
{
builder.Append(allChars[i]);
}
else
{
int index = builder.Length;
int count = 0;
do
{
builder.Append(validChars[c % 59]);
c /= 59;
count++;
} while (c > 0);
if (count == 1) builder.Insert(index, '0');
else if (count == 2) builder.Insert(index, '1');
else if (count == 3) builder.Insert(index, '2');
else throw new Exception("Base59 has invalid count, method must be wrong Count is: " + count);
}
}
return builder.ToString();
答案 5 :(得分:0)
URL编码/解码功能如何?它会处理'/'
,'?'
和'#'
个字符。
string url = "http://www.google.com/search?q=Example";
string key = HttpUtility.UrlEncode(url);
string urlBack = HttpUtility.UrlDecode(key);