如何:區塊序列化資料How to: chunk serialized data

警告

二進位序列化可能帶來危害。Binary serialization can be dangerous. 請勿還原序列化來自未受信任之來源的資料,而且也不要在不在您控制之下的系統間反覆存取序列化資料。Never deserialize data from an untrusted source and never round-trip serialized data to systems not under your control.

在 Web 服務訊息中發送大型資料集時發生的兩項問題為:Two issues that occur when sending large data sets in Web service messages are:

  1. 由於序列化引擎緩衝而產生的大型工作集 (記憶體)。A large working set (memory) due to buffering by the serialization engine.

  2. 由於 Base64 編碼後暴增 33%,因此過度消耗頻寬。Inordinate bandwidth consumption due to 33 percent inflation after Base64 encoding.

若要解決這些問題,請實作 IXmlSerializable 介面以控制序列化與還原序列化。To solve these problems, implement the IXmlSerializable interface to control the serialization and deserialization. 具體地說,實作 WriteXmlReadXml 方法將資料區分成區塊。Specifically, implement the WriteXml and ReadXml methods to chunk the data.

實作伺服器端區分區塊的功能To implement server-side chunking

  1. 在伺服器機器上,Web 方法必須關閉 ASP.NET 緩衝並傳回實作 IXmlSerializable的型別。On the server machine, the Web method must turn off ASP.NET buffering and return a type that implements IXmlSerializable.

  2. 實作 IXmlSerializable 的型別會將 WriteXml 方法中的資料區分區塊。The type that implements IXmlSerializable chunks the data in the WriteXml method.

實作用戶端處理To implement client-side processing

  1. 變更用戶端 Proxy 上的 Web 方法以傳回實作 IXmlSerializable的型別。Alter the Web method on the client proxy to return the type that implements IXmlSerializable. 您可以使用 SchemaImporterExtension 自動執行這個作業,不過在此不予以討論。You can use a SchemaImporterExtension to do this automatically, but this isn't shown here.

  2. 實作 ReadXml 方法讀取區分成區塊的資料流並將位元組寫入磁碟。Implement the ReadXml method to read the chunked data stream and write the bytes to disk. 此實作也引發圖形控制項可使用的進度事件,例如進度列。This implementation also raises progress events that can be used by a graphic control, such as a progress bar.

範例Example

下列程式碼範例顯示關閉 ASP.NET 緩衝之用戶端上的 Web 方法。The following code example shows the Web method on the client that turns off ASP.NET buffering. 它也顯示用戶端 IXmlSerializable 介面的實作,將 WriteXml 方法中的資料區分區塊。It also shows the client-side implementation of the IXmlSerializable interface that chunks the data in the WriteXml method.

[WebMethod]
[System.Web.Services.Protocols.SoapDocumentMethodAttribute
(ParameterStyle= SoapParameterStyle.Bare)]
public SongStream DownloadSong(DownloadAuthorization Authorization, string filePath)
{

// Turn off response buffering.
System.Web.HttpContext.Current.Response.Buffer = false;
// Return a song.
SongStream song = new SongStream(filePath);
return song;
}
    <WebMethod(), System.Web.Services.Protocols.SoapDocumentMethodAttribute(ParameterStyle := SoapParameterStyle.Bare)>  _
    Public Function DownloadSong(ByVal Authorization As DownloadAuthorization, ByVal filePath As String) As SongStream 
        
        ' Turn off response buffering.
        System.Web.HttpContext.Current.Response.Buffer = False
        ' Return a song.
        Dim song As New SongStream(filePath)
        Return song
    
    End Function
End Class
[XmlSchemaProvider("MySchema")]
public class SongStream : IXmlSerializable
{

    private const string ns = "http://demos.Contoso.com/webservices";
    private string filePath;

    public SongStream(){ }

    public SongStream(string filePath)
    {
     this.filePath = filePath;
    }

    // This is the method named by the XmlSchemaProviderAttribute applied to the type.
    public static XmlQualifiedName MySchema(XmlSchemaSet xs)
    {
     // This method is called by the framework to get the schema for this type.
     // We return an existing schema from disk.

     XmlSerializer schemaSerializer = new XmlSerializer(typeof(XmlSchema));
     string xsdPath = null;
     // NOTE: replace the string with your own path.
     xsdPath = System.Web.HttpContext.Current.Server.MapPath("SongStream.xsd");
     XmlSchema s = (XmlSchema)schemaSerializer.Deserialize(
         new XmlTextReader(xsdPath), null);
     xs.XmlResolver = new XmlUrlResolver();
     xs.Add(s);

     return new XmlQualifiedName("songStream", ns);
    }


    void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
    {
       // This is the chunking code.
       // ASP.NET buffering must be turned off for this to work.
 

     int bufferSize = 4096;
     char[] songBytes = new char[bufferSize];
     FileStream inFile = File.Open(this.filePath, FileMode.Open, FileAccess.Read);

     long length = inFile.Length;

     // Write the file name.
     writer.WriteElementString("fileName", ns, Path.GetFileNameWithoutExtension(this.filePath));

     // Write the size.
     writer.WriteElementString("size", ns, length.ToString());

     // Write the song bytes.
     writer.WriteStartElement("song", ns);

     StreamReader sr = new StreamReader(inFile, true);
     int readLen = sr.Read(songBytes, 0, bufferSize);

     while (readLen > 0)
     {
         writer.WriteStartElement("chunk", ns);
         writer.WriteChars(songBytes, 0, readLen);
         writer.WriteEndElement();

         writer.Flush();
         readLen = sr.Read(songBytes, 0, bufferSize);
     }

     writer.WriteEndElement();
     inFile.Close();

    }


    System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
    {
     throw new System.NotImplementedException();
    }

    void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
    {
     throw new System.NotImplementedException();

    }
}
<XmlSchemaProvider("MySchema")>  _
Public Class SongStream
    Implements IXmlSerializable
    
    Private Const ns As String = "http://demos.Contoso.com/webservices"
    Private filePath As String
    
    Public Sub New() 
    
    End Sub
     
    Public Sub New(ByVal filePath As String) 
        Me.filePath = filePath
    End Sub
    
    
    ' This is the method named by the XmlSchemaProviderAttribute applied to the type.
    Public Shared Function MySchema(ByVal xs As XmlSchemaSet) As XmlQualifiedName 
        ' This method is called by the framework to get the schema for this type.
        ' We return an existing schema from disk.
        Dim schemaSerializer As New XmlSerializer(GetType(XmlSchema))
        Dim xsdPath As String = Nothing
        ' NOTE: replace SongStream.xsd with your own schema file.
        xsdPath = System.Web.HttpContext.Current.Server.MapPath("SongStream.xsd")
        Dim s As XmlSchema = CType(schemaSerializer.Deserialize(New XmlTextReader(xsdPath)), XmlSchema)
        xs.XmlResolver = New XmlUrlResolver()
        xs.Add(s)
        
        Return New XmlQualifiedName("songStream", ns)
    
    End Function
    
    
    
    Sub WriteXml(ByVal writer As System.Xml.XmlWriter)  Implements IXmlSerializable.WriteXml
        ' This is the chunking code.
        ' ASP.NET buffering must be turned off for this to work.
        
        Dim bufferSize As Integer = 4096
        Dim songBytes(bufferSize) As Char
        Dim inFile As FileStream = File.Open(Me.filePath, FileMode.Open, FileAccess.Read)
        
        Dim length As Long = inFile.Length
        
        ' Write the file name.
        writer.WriteElementString("fileName", ns, Path.GetFileNameWithoutExtension(Me.filePath))
        
        ' Write the size.
        writer.WriteElementString("size", ns, length.ToString())
        
        ' Write the song bytes.
        writer.WriteStartElement("song", ns)
        
        Dim sr As New StreamReader(inFile, True)
        Dim readLen As Integer = sr.Read(songBytes, 0, bufferSize)
        
        While readLen > 0
            writer.WriteStartElement("chunk", ns)
            writer.WriteChars(songBytes, 0, readLen)
            writer.WriteEndElement()
            
            writer.Flush()
            readLen = sr.Read(songBytes, 0, bufferSize)
        End While
        
        writer.WriteEndElement()
        inFile.Close()
    End Sub 
        
    Function GetSchema() As System.Xml.Schema.XmlSchema  Implements IXmlSerializable.GetSchema
        Throw New System.NotImplementedException()
    End Function
    
    Sub ReadXml(ByVal reader As System.Xml.XmlReader)  Implements IXmlSerializable.ReadXml
        Throw New System.NotImplementedException()
    End Sub 
End Class 

public class SongFile : IXmlSerializable
{
    public static event ProgressMade OnProgress;

    public SongFile()
    { }

    private const string ns = "http://demos.teched2004.com/webservices";
    public static string MusicPath;
    private string filePath;
    private double size;

    void IXmlSerializable.ReadXml(System.Xml.XmlReader reader)
    {
        reader.ReadStartElement("DownloadSongResult", ns);
        ReadFileName(reader);
        ReadSongSize(reader);
        ReadAndSaveSong(reader);
        reader.ReadEndElement();
    }

    void ReadFileName(XmlReader reader)
    {
        string fileName = reader.ReadElementString("fileName", ns);
        this.filePath = 
            Path.Combine(MusicPath, Path.ChangeExtension(fileName, ".mp3"));
    }

    void ReadSongSize(XmlReader reader)
    {
        this.size = Convert.ToDouble(reader.ReadElementString("size", ns));
    }

    void ReadAndSaveSong(XmlReader reader)
    {
        FileStream outFile = File.Open(
            this.filePath, FileMode.Create, FileAccess.Write);

        string songBase64;
        byte[] songBytes;
        reader.ReadStartElement("song", ns);
        double totalRead=0;
        while(true)
        {
            if (reader.IsStartElement("chunk", ns))
            {
                  songBase64 = reader.ReadElementString();
                  totalRead += songBase64.Length;
                  songBytes = Convert.FromBase64String(songBase64);
                  outFile.Write(songBytes, 0, songBytes.Length);
                  outFile.Flush();

                  if (OnProgress != null)
                  {
                      OnProgress(100 * (totalRead / size));
                  }
            }

            else
            {
                  break;
            }
        }

        outFile.Close();
        reader.ReadEndElement();
    }

    [PermissionSet(SecurityAction.Demand, Name="FullTrust")]
    public void Play()
    {
        System.Diagnostics.Process.Start(this.filePath);
    }

    System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
    {
        throw new System.NotImplementedException();
    }

    public void WriteXml(XmlWriter writer)
    {
        throw new System.NotImplementedException();
    }
}
Public Class SongFile
    Implements IXmlSerializable
    Public Shared Event OnProgress As ProgressMade
    
    
    Public Sub New() 
    
    End Sub 
    
    Private Const ns As String = "http://demos.teched2004.com/webservices"
    Public Shared MusicPath As String
    Private filePath As String
    Private size As Double
    
    Sub ReadXml(ByVal reader As System.Xml.XmlReader)  Implements IXmlSerializable.ReadXml
        reader.ReadStartElement("DownloadSongResult", ns)
        ReadFileName(reader)
        ReadSongSize(reader)
        ReadAndSaveSong(reader)
        reader.ReadEndElement()
    End Sub 
    
    Sub ReadFileName(ByVal reader As XmlReader) 
        Dim fileName As String = reader.ReadElementString("fileName", ns)
        Me.filePath = Path.Combine(MusicPath, Path.ChangeExtension(fileName, ".mp3"))
    
    End Sub 
    
    Sub ReadSongSize(ByVal reader As XmlReader) 
        Me.size = Convert.ToDouble(reader.ReadElementString("size", ns))
    
    End Sub 
    
    Sub ReadAndSaveSong(ByVal reader As XmlReader) 
        Dim outFile As FileStream = File.Open(Me.filePath, FileMode.Create, FileAccess.Write)
        
        Dim songBase64 As String
        Dim songBytes() As Byte
        reader.ReadStartElement("song", ns)
        Dim totalRead As Double = 0
        While True
            If reader.IsStartElement("chunk", ns) Then
                songBase64 = reader.ReadElementString()
                totalRead += songBase64.Length
                songBytes = Convert.FromBase64String(songBase64)
                outFile.Write(songBytes, 0, songBytes.Length)
                outFile.Flush()
                RaiseEvent OnProgress((100 *(totalRead / size)))
            Else
                Exit While
            End If
        End While
        
        outFile.Close()
        reader.ReadEndElement()
    End Sub 
    
    <PermissionSet(SecurityAction.Demand, Name :="FullTrust")> _
    Public Sub Play() 
        System.Diagnostics.Process.Start(Me.filePath)
    End Sub 
    
    
    Function GetSchema() As System.Xml.Schema.XmlSchema  Implements IXmlSerializable.GetSchema
        Throw New System.NotImplementedException()
    End Function 
    
    
    Public Sub WriteXml(ByVal writer As XmlWriter) Implements IXmlSerializable.WriteXml
        Throw New System.NotImplementedException()
    End Sub 
End Class 

編譯程式碼Compiling the code

另請參閱See also