2014-01-15 79 views
10

我正在使用Azure存儲表,並且有數據進入其中已有斜線的RowKey。根據this MSDN page,以下字符不允許在兩個PartitionKey和RowKey:如何編碼Azure存儲錶行鍵和分區鍵?

  • 正斜槓(/)字符

  • 反斜線字符()

  • 數字符號(#)字符

  • 問號(?)字符

  • 0123從U個
  • 控制字符,以U + 001F,包括:

  • 水平製表符(\ t)的字符

  • 換行(\ n)的字符

  • 回車( \距離U r)的字符

  • 控制字符+ 007F到U + 009F

我見過一些人使用URL編碼來解決這個問題。不幸的是,可能會產生一些小故障,比如能夠插入但無法刪除某些實體。我也看到一些人使用base64編碼,但是這也可以包含不允許的字符。

我怎樣纔能有效地編碼我的RowKey,而不會跑入不允許的字符或滾動我自己的編碼?

+0

「但無法刪除某些實體」爲什麼會這樣? – usr

+0

@usr這是一個錯誤。不知道爲什麼,但我看到了它的多個報告。 –

回答

10

當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('/','_'); 
} 

解碼時,只需撤消替換字符(first!),然後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字符也需要編碼。根據Azure Table Storage docs,情況並非如此。

1

看到這些鏈接 http://tools.ietf.org/html/rfc4648#page-7 Code for decoding/encoding a modified base64 URL(見第二個答案:https://stackoverflow.com/a/1789179/1094268

我有自己的問題。這些是我現在使用的自己的功能。我在我提到的第二個答案中使用了這個技巧,並且更改了+/這些與仍然可能出現的天藍鍵不兼容的問題。

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

根據Azure文檔,「+」字符在Azure Table關鍵字段中不是無效的。 –

+1

@JasonWeber從我最初做這件事以來已經有一段時間了,但我很確定我記得我讀過的是(或者)是一個無證件的例外。 –

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

    } 
} 
+0

是的,我認爲不丟失鑰匙的可讀性是非常重要的。驚訝的是,更多的人不這樣做或URLencode,然後修復任何不符合Base64編碼和修復的東西,這似乎是這裏的一般方法。 – Rory

+1

這裏需要注意的一點是,從v0.8.3開始的Microsoft Azure存儲資源管理器無法通過PartitionKey eq查詢對象,如果您有! PartitionKey中的字符。在引擎蓋下,東西似乎工作正常,但由於某種原因,等號運算符無法正常工作。我將此報告爲一個錯誤,但請記住,如果您使用此解決方案和該特定字符。 –

+0

我懷疑這是因爲自然鍵只在某些情況下才有意義,可讀性通常/通常不會使代理鍵受益。 –

0

URL編碼/解碼功能如何。它需要照顧'/','?''#'個字符。

string url = "http://www.google.com/search?q=Example"; 
string key = HttpUtility.UrlEncode(url); 
string urlBack = HttpUtility.UrlDecode(key); 
1

我所看到的是,雖然很多的非字母數字字符在技術上允許它並沒有真正很好地工作的分區和行鍵。

我看着這裏已經給出的answears等地寫了這樣: 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"); 

但是,如果你想使用例如的電子郵件地址作爲RO wkey你只想逃避'@'和'。'。此代碼將這樣做:

 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(); 
相關問題