C# 如何将MIME编码的base64字符串返回到可写字节数组?

C# 如何将MIME编码的base64字符串返回到可写字节数组?,c#,encoding,base64,C#,Encoding,Base64,我在数据库中有许多PDF、doc、docx、jpg等文件,这些文件是使用以下方法添加的: 附件以MIME编码base64编码的形式发送到数据库 串。然后,组件将此MIME编码的字符串转换为 在将字节流作为BLOB写入数据库之前,将其转换为unicode Oracle或映像SQL Server 字符串的开头还附加了一个“guid”,即76个字符 我试图将附件解压缩并保存到磁盘上的文件中,而不是数据库中。它在大约20%的时间里工作。我获取System.FormatException的剩余时间:Bas

我在数据库中有许多PDF、doc、docx、jpg等文件,这些文件是使用以下方法添加的:

附件以MIME编码base64编码的形式发送到数据库 串。然后,组件将此MIME编码的字符串转换为 在将字节流作为BLOB写入数据库之前,将其转换为unicode Oracle或映像SQL Server

字符串的开头还附加了一个“guid”,即76个字符

我试图将附件解压缩并保存到磁盘上的文件中,而不是数据库中。它在大约20%的时间里工作。我获取System.FormatException的剩余时间:Base-64字符串中的无效字符。将我的字符串传递到FromBase64String时

我注意到,数据库中以如下方式启动的值成功保存:

0x7B0035003000310041004600320033035004602D00370

失败的总是这样开始的:

0x7B35303146323335462D373546302D343936342D394

我这里没有足够的字符来粘贴完整的示例,因此请参阅一个不起作用的示例。它应该表示一个Word文档,该文档仅表示测试文档。是相同的文档,但已转换为PDF

将正常工作并转换为test font.htm。它必须插入SQL数据库中的图像列中,然后用我的代码取出:

 private const int guidLength = 38 * 2;
 public static byte[] GetAttachment(string folderid, string filename) {
 string queryString = string.Format("SELECT <image column> FROM AttachmentTable WHERE .....",
                      folderid, filename);
                using (SqlConnection connection = new SqlConnection("context connection=true"))
                {
                    connection.Open();
                    using (SqlCommand selectAttachment = new SqlCommand(
                        queryString,
                        connection))
                    {
                        using (SqlDataReader reader = selectAttachment.ExecuteReader())                        {
                            while (reader.Read())
                            {
                                if (reader[0] == System.DBNull.Value)
                                    return new byte[0];
                                byte[] data = (byte[])reader[0];
                                byte[] truncatedData;
                                if (data[data.Length - 2] == 0)
                                    truncatedData = new byte[data.Length - guidLength - 2];
                                else
                                    truncatedData = new byte[data.Length - guidLength];
                                Array.Copy(data, guidLength, truncatedData, 0, truncatedData.Length);
                                // base64 unencode
                                string truncatedString = Encoding.Unicode.GetString(truncatedData);
                                return Convert.FromBase64String(truncatedString);
                            }
                        }

                    }
                } 

             }
SQL CLR函数

   [SqlFunction(IsDeterministic = true,
                     IsPrecise = true,
                     DataAccess = DataAccessKind.Read,
                     SystemDataAccess = SystemDataAccessKind.Read)]
    public static SqlString WriteToFile(SqlString path, SqlString folderid, SqlString fileName)
    {
        try
        {
            if (!path.IsNull && !folderid.IsNull && !fileName.IsNull)
            {
                var dir = Path.GetDirectoryName(path.Value);
                if (!Directory.Exists(dir))
                    Directory.CreateDirectory(dir);
                string filename = Convert.ToString(fileName);
                string folderid = Convert.ToString(efolderid);
                string filepath = Convert.ToString(path);
                SaveAttachmentToFile(filename, folderid, filepath);
                return "Wrote file";
            }
            else
                return "No data passed to method!";
        }
        catch (IOException e)
        {
            return "Make sure the assembly has external access!\n" + e.ToString();
        }
        catch (Exception ex)
        {
            return ex.ToString();
        }
    }
注意,上面的所有C代码都编译成一个程序集,然后用作CLR函数:

CREATE FUNCTION [dbo].[WriteToFile](@path [nvarchar](max), @efolderid [nvarchar](max), @filename [nvarchar](max))
RETURNS [nvarchar](max) WITH EXECUTE AS CALLER
AS 
EXTERNAL NAME [ClassLibrary1].[CLR.UserDefinedFunctions].[WriteToFile]
GO
我认为我的问题可能与编码有关。我想我可能可以使用Encoding.MIME.GetString,但它不存在。我也尝试过UTF-8,但成功率为0%。Unicode似乎有效,但如上所述,成功率约为20%


我的问题是,为什么其中一些无法保存不正确的base64字符。。但是为什么呢?还有其他人工作得很好吗?如何确定要使用的正确编码?它有一个模式,但我真的不确定如何从这里开始。

给定的数据插入方法不明确;unicode实际上不是一种文本编码;它是将符号表示为数字的通用系统。Net framework确实有一种称为Unicode的编码,但这是一种误称,这种编码实际上是UTF-16

现在,如前所述,您的数据有两种格式;一个有效,一个无效。这两种格式的区别在于,其中一种格式的每个数据字节之间有00个字节。这对应于UTF-16-LE,其中所有符号都是16位,即2个字节,值的最低部分存储在第一个字节中。没有这00字节的压缩数据应该是纯ASCII

这种UTF-16格式实际上是保存Base64数据的一种相当愚蠢的方式,因为Base64从定义上讲始终是纯7位ascii;这些额外的字节永远不会被使用,只需将保存数据所需的空间增加一倍。事实上,当保存为字节时,Base64编码也没有任何优点,因为Base64的目的是将二进制数据转换为纯文本,这样就可以由无法处理存储/传输二进制数据的系统来处理。鉴于此Base64文本随后被保存为数据库中的二进制LOB,这里的情况显然不是这样

除此之外,00字节确实为您的问题提供了一个解决方案:正如我所说的,对于Base64内容,这些介于字节之间的内容将永远不会被使用,这意味着它们将始终是00。另一方面,Base64始终是纯ASCII文本,不应包含00字节。这意味着您可以检查这00个字节,并使用它们的存在来选择正确的编码

请注意,在将字节转换为字符串后切断GUID要简单得多,因为这样它的长度总是38,而不是ASCII中的38字节或UTF-16中的76字节

使第一个代码块的读取器部分适应这一点应该可以解决问题:

using (SqlDataReader reader = selectAttachment.ExecuteReader())
{
    // only reading one anyway; doesn't need to be a 'while'.
    if (!reader.Read())
        return new byte[0];
    if (reader[0] == System.DBNull.Value)
        return new byte[0];
    byte[] data = (byte[])reader[0];
    if (data.Length == 0)
        return new byte[0];
    String base64String
    if (data.Length > 1 && data[1] == 00)
        base64String = Encoding.Unicode.GetString(data);
    else
        base64String = Encoding.ASCII.GetString(data);
    // Cuts off the GUID, and takes care of any trailing 00 bytes.
    String truncatedString = base64String.Substring(38).TrimEnd('\0');
    return Convert.FromBase64String(truncatedString);
}

在我看来,这些不像base64字符串。它们看起来像是由十六进制对表示的字节序列。也许,但其中一些确实转换为base64,然后我可以将文件保存到磁盘,这并不意味着您最终会得到任何类型的有效数据。尝试在开始时切断0x,然后每两次迭代一次,将它们从十六进制字符串转换为字节值,并保存生成的字节数组。@nyrguds Nope-不确定如何处理。您是否大致了解如何从十六进制字符串部分实现字节值,例如,它是一个内置的.net函数还是我需要自己编写的东西?从您的问题中可以看出,0x格式只是您用来检查数据库的数据是如何显示的,而不是您实际如何在程序中获取数据。虽然通过仔细阅读代码本身,这肯定是可以推断出来的,所以我为我的粗鲁道歉。非常感谢,这非常有效,我感谢详细的解释。我昨天很生气,因为这件事已经过去了
对我的批评如此之多,我为这番咄咄逼人的评论道歉。事实上,我自己从来没有想到这一点,所以我欠你一个人情是的,那件事有点混乱。。。我盲目地跟随已经发布的关于这一点的评论,而没有真正彻底阅读代码。对不起,很好。我很感谢您花了这么多时间来帮助解决这个问题。实际上,在将两个十六进制示例转储到在线十六进制到文本转换器中之后,我发现了这个问题,并注意到作为这些guid开头的结果文本对于这两个guid都是相同的,但对于工作示例,转换的字符串要短得多,尽管输入大小相同。这时我注意到了00字节,转换器简单地抛出了它,因为它们不是有效的字母。
using (SqlDataReader reader = selectAttachment.ExecuteReader())
{
    // only reading one anyway; doesn't need to be a 'while'.
    if (!reader.Read())
        return new byte[0];
    if (reader[0] == System.DBNull.Value)
        return new byte[0];
    byte[] data = (byte[])reader[0];
    if (data.Length == 0)
        return new byte[0];
    String base64String
    if (data.Length > 1 && data[1] == 00)
        base64String = Encoding.Unicode.GetString(data);
    else
        base64String = Encoding.ASCII.GetString(data);
    // Cuts off the GUID, and takes care of any trailing 00 bytes.
    String truncatedString = base64String.Substring(38).TrimEnd('\0');
    return Convert.FromBase64String(truncatedString);
}