MSDN Magazin > Home > Ausgaben > 2007 > August >  OPC: Ein neuer Standard für das Verpacken ...
OPC
Ein neuer Standard für das Verpacken Ihrer Daten
Jack Davis and Andrey Shur

Themen in diesem Artikel:
  • Wie sind Inhalte in einem Paket organisiert?
  • Teile und Beziehungen
  • Adressierbare URIs
  • Erstellen eines eigenen OPC-Dateiformats
In diesem Artikel werden folgende Technologien verwendet:
Open Packaging Conventions, .NET Framework 3.0
Laden Sie den Code für diesen Artikel herunter: Packaging2007_08.exe (984 KB)
Code online durchsuchen
Viele Anwendungen integrieren Inhalte in verschiedene zusätzliche Ressourcen. Ein Webbrowser zeigt beispielsweise eine Seite an, in die HTML, Bilddateien, Stylesheets und andere Arten von Inhalten integriert sind. In ähnlicher Weise wird durch ein Textverarbeitungsprogramm ein Dokument erstellt, das Text, Formatvorlagen, Bilddateien und weitere Elemente enthält. Für die Organisation von Inhalten gibt es im Wesentlichen zwei Ansätze: Bei der Flatfileorganisation werden Inhalte als separate Dateien auf einem Datenträger gespeichert. Bei binären Containerdateien wird der gesamte Inhalt in einer benutzerdefinierten Datei verpackt. Der zweite Ansatz setzt sich bei Anwendungen immer mehr durch.
Gemäß dem Trend zu offenen Standards wurde eine neue Technologie für die Erstellung von Dateipaketen als Teil der Open XML-Spezifikation von 2007 Microsoft® Office System entwickelt. Diese ist kürzlich von der Standardisierungsorganisation ECMA als internationaler Standard anerkannt worden. Eine diesem Standard zugrunde liegende Komponente ist Open Packaging Conventions (OPC). Sie definiert eine strukturierte Methode zum Speichern von Anwendungsdaten und zugehörigen Ressourcen in einer standardmäßigen ZIP-Datei. Diese neue Paketerstellungstechnologie wird bereits in mehreren Microsoft-Produkten verwendet, einschließlich der 2007 Office System-Anwendungen. Die XML Paper Specification (XPS), die das neue Druckspooling- und Dokumentdarstellungsformat für Windows Vista™ definiert, implementiert auch die Speicherung und den Transport von Dokumenten mit hoher Wiedergabetreue auf der Basis von OPC. Weitere Informationen zu den in diesem Artikel besprochenen Technologien finden Sie in der Randleiste „Onlineressourcen“.
Wodurch unterscheidet sich also nun die von Open Packaging Conventions bereitgestellte Technologie übertragbarer Container? Da es sich um einen offenen Standard handelt, bietet OPC eine Containertechnologie, die Sie verwenden können, ohne selbst benutzerdefinierte binäre Containerdateien programmieren zu müssen. OPC unterstützt zudem einige erweiterte Features, einschließlich inhaltsadressierbarer URIs, MIME-Typen, relationaler Strukturierung sowie Authentifizierung und Gültigkeitsprüfung. Mit Microsoft .NET Framework 3.0 bieten die Paket-APIs bei der Rechteverwaltung auch Optionen für die Verschlüsselung. Da sie einem offenen Standard entsprechen, kann auf paketbasierte Dateien zudem über Dienste einer höheren Ebene wie Workflowanwendungen und Virenscanner zugegriffen werden.
In Windows Vista ist API-Unterstützung für die Open Packaging Conventions der ECMA integriert und als Teil von .NET Framework 3.0 zur Verwendung mit Windows® XP und Windows Server® 2003 enthalten. In diesem Artikel wird der neue Standard untersucht, und Sie erfahren, wie Sie mit den .NET Framework 3.0-APIs bei Ihrer Anwendung die Speicherung mehrerer Datenströme in einem einzelnen übertragbaren Paket organisieren können.

Organisation des Dateiformats
Obwohl sowohl in Microsoft Word 2007 als auch in Excel® 2007 Open Packaging zum Speichern von Dokumenten verwendet wird, verwenden die beiden Anwendungen unterschiedliche Schemas und Dateiorganisationen. Die spezifische Organisation der Inhalte eines Pakets definiert sein Format, das normalerweise an der Dateierweiterung erkennbar ist (z. B. .docx oder .xlsx).
Bei der Paketerstellung können Sie Ihre eigene Dateiorganisation, Dateinamenerweiterung und Dateitypzuordnung für Ihre Anwendung definieren. Als Beispiel erstellen wir einen eigenen benutzerdefinierten Pakettyp, .htmx, der eine Webseite zusammen mit den lokalen Dateien speichert, von denen sie abhängig ist (Stylesheets, Skripts, Bilddateien usw.).

Pakete
Ein Paket ist die grundlegende Speichereinheit des Open Packaging Conventions-Standards. In der Implementierung für .NET Framework 3.0 ist System.IO.Packaging.Package als abstrakte Klasse definiert, von der bestimmte physische Implementierungen abgeleitet werden. Die standardmäßige und primäre physische Implementierung eines Pakets in .NET Framework 3.0 ist die abgeleitete Klasse „ZipPackage“, die ein Standard-ZIP-Archiv (siehe Abbildung 1) verwendet. Das folgende Codebeispiel zeigt die grundlegenden Schritte zum Erstellen und Öffnen eines neuen Pakets:
Abbildung 1 Dateiformate, die Open Packaging Conventions verwenden 
using System.IO.Packaging;
...
// Path and name for the package file.
string packageFile = @”C:\webpages\packaging.htmx”;

// Create and open the specified package file for writing.
// (The ‘using’ statement ensures that package is
//  closed and disposed when it goes out of scope.)
using (Package package = Package.Open(packageFile, FileMode.Create))
{
    ... // Store content files as parts in the package.
}
Bei Open Packaging werden ZIP-Dateien verwendet, da sie ein bekanntes Format nach Industriestandard sind, mit dem es sich einfach arbeiten lässt, das leicht zu prüfen ist und das problemlosen Zugriff ermöglicht. Sie können eine paketbasierte Datei (z. B. eine .docx-Datei) nehmen, diese durch Anhängen einer ZIP-Erweiterung umbenennen und mithilfe eines standardmäßigen ZIP-Programms wie dem in Windows Explorer integrierten Feature für komprimierte Ordner auf ihren Inhalt zugreifen.
In einem Paket werden zwei grundlegende Elemente gespeichert: PackageParts (die einfach als Teile bezeichnet werden) und PackageRelationships (die als Beziehungen bezeichnet werden). Die Teile stellen den tatsächlich zu verpackenden Inhalt dar (siehe Abbildung 2). Komponenten höherer Ebenen bauen auf den grundlegenden Teil- und Beziehungselementen auf. Dazu gehören PackageDigitalSignatures, PackageProperties und Komponenten zur Unterstützung von Verschlüsselung und Rechteverwaltung.
Abbildung 2 Grundlegende Elemente eines Pakets 

Paketteile
Ein Teil ist ein Datenstrom, analog zu einer Datei in einem Dateisystem oder ZIP-Archiv. Teile können beliebige Datentypen enthalten, beispielsweise Binärdaten, Text, Bilder usw. Abbildung 3 zeigt ein Beispielpaket, das mehrere Teile mit verschiedenen Arten von Inhalten enthält.
Abbildung 3 Verpacken verschiedener Arten von Inhalten 
Wird ein Teil in einem Paket gespeichert, wird sie durch einen eindeutigen URI-formatierten PartName zusammen mit einem MIME ContentType definiert. Teilnamen beginnen mit einem Schrägstrich (/), der einen absoluten Pfad ausgehend vom Paketstammverzeichnis angibt. Sie können mühelos ein Teil dem Paket hinzufügen oder aus ihm entfernen, in ein Teil schreiben, dieses aktualisieren oder es aus dem Paket lesen, indem Sie einfach auf den eindeutigen URI-Teilnamen verweisen.
Obwohl die PartName-URIs scheinbar eine Ordnerhierarchie darstellen, gibt es in einem Paket keine separaten Ordner. Ein Paket kann keine leeren Ordner enthalten, und das Konzept eines aktuellen Standardverzeichnisses existiert nicht. Da es keine leeren Ordner geben darf, müssen Ordner auch nicht separat erstellt, gelöscht oder anderweitig verwaltet werden. Wenn das Paket in einem grafischen ZIP-Programm geöffnet wird, zeigt das Dienstprogramm oft die Pfade der Teilnamen in einer Ordnerdarstellung an. Auf ähnliche Art und Weise erstellt das Dienstprogramm beim Entzippen des Pakets die scheinbare Ordnerhierarchie erneut. Die Ordnerdarstellung ist jedoch ein künstliches Produkt des ZIP-Programms und im Paket selbst nicht vorhanden.
Das Codebeispiel in Abbildung 4 zeigt, wie zunächst ein Teil erstellt wird und dann Daten aus einer Datenträgerdatei in das im Paket gespeicherte Teil geschrieben werden.

Beziehungen
Eine Beziehung definiert eine Zuordnung zwischen zwei Elementen: einer angegebenen Quelle und einem angegebenen Ziel. Die Quelle einer Beziehung kann entweder das Paket selbst (Beziehung auf Paketebene) oder ein bestimmtes Teil im Paket (Beziehung auf Teilebene) sein. Das Ziel einer Beziehung kann entweder ein bestimmtes Teil im Paket oder eine bestimmte externe Ressource sein. Eine externe Ressource kann ein anderes Paket, ein Teil in einem anderen Paket oder ein anderer über URI adressierbarer Datenentitätstyp sein. Folglich können vier Beziehungskombinationen definiert werden:
  • Zwischen einem Paket (Quelle) und einem bestimmten Teil (Ziel) im Paket.
  • Zwischen einem Paket (Quelle) und einer bestimmten externen Ressource (Ziel), d. h. einer Ressource, die nicht im Paket enthalten ist.
  • Zwischen einem bestimmten Teil (Quelle) innerhalb des Pakets und einem zweiten festgelegten Teil (Ziel), das ebenfalls im Paket enthalten ist.
  • Zwischen einem bestimmten Teil (Quelle) im Paket und einer angegebenen externen Ressource (Ziel).
Das Quellteil wird als Besitzer einer Beziehung betrachtet. Wenn das Quellteil gelöscht wird, werden auch alle zu diesem Teil gehörenden Beziehungen gelöscht. Beziehungen werden in speziellen Beziehungsteilen definiert und separat im Paket gespeichert Beachten Sie, dass die Quelle bzw. die Zielelemente durch das Erstellen oder Löschen einer Beziehung physisch in keiner Weise verändert werden.
Beziehungen bieten eine Reihe von Vorteilen:

Relationale Strukturierung Mithilfe von Beziehungen können Zuordnungen zwischen Inhalten und Ressourcen festgelegt werden, ohne dass Inhaltsdatenströme gelesen oder analysiert werden müssen. Durch die Vorverarbeitung von Aufgaben können benötigte Ressourcen aggregiert und zwischengespeichert werden, oder es kann darauf zugegriffen werden, ohne dass Details über den verwendeten Inhalt bekannt sein müssen.
Erkennbarkeit des Inhalts Beziehungstypen ermöglichen in Kombination mit Teil-MIME-Inhaltstypen die schnelle Erkennung der Struktur, Zuordnungen und Inhalte der im Paket enthaltenen Teile.
Schemaunabhängigkeit Von Beziehungen definierte Inhalts- und Ressourcenzuordnungen sind schemaunabhängig. Dies ist besonders hilfreich für in XML-Markup definierte Inhalte, deren Analyse normalerweise die Kenntnis des verwendeten Schemas erfordert.
Verweisintegrität Für XML-basierte Inhalte können Sie Ressourcen im Markup durch einen Verweis auf eine Beziehungs-ID angeben und müssen keine Ressourcen-URI-Verweise direkt ins Markup einbetten. Dadurch wird das XML-Markup vereinfacht und die Möglichkeit ausgeschlossen, dass Inhalts- und Beziehungsverweise nicht mehr synchronisiert sind.
In Abbildung 5 sehen Sie eine HTML-Seite, die mit ihren Stylesheets und Bilddateiressourcen verpackt wurde sowie die Beziehungen, die die Zuordnungen definieren. Mit der Beziehung auf Paketebene wird der URI des PackagePart-Teils identifiziert, das den HTML-Basisinhalt für die Seite enthält. Über eine Abfrage der Beziehung „root-html“ des Pakets kann der URI des HTML-Webseitenteils direkt bestimmt werden. Mit dem HTML-Teil als Quelle können die Beziehungen auf Teilebene für das Stylesheet, die Skripts und die Bildressourcen leicht gefunden und aufgelöst werden. (Für jede Ressource, auf die in der HTML-Seite verwiesen wird, gibt es eine entsprechende Beziehung auf Teilebene.)
Abbildung 5 Beispiel für ein Paket mit einer HTML-Seite (Klicken Sie zum Vergrößern auf das Bild)
Der Code in Abbildung 6 zeigt, wie Sie ein Paket erstellen, ein Teil erstellen und hinzufügen und dann eine Paketbeziehung erstellen.
using System.IO.Packaging;

// Path and name of the package file.
string packageFile = @”C:\webpages\packaging.htmx”; 

// Path and name of the file to store in the package.
string partFile = @”C:\inetpub\wwwroot\packaging.htm”;

// Part name URI, MIME content type, and compression option.
Uri partNameUri = PackUriHelper.CreatePartUri(
    new Uri(“/packaging.htm”, UriKind.Relative));
string partType = “text/html”; // MIME type
CompressionOption compression = CompressionOption.Normal;

 for the package relationship type.
string packageRelationshipType = “http://schemas.openxmlformats.org/” +
    “package/2007/relationships/htmx/root-html”;

// Create and open the package file.
using (Package package = Package.Open(packageFile, FileMode.Create))
{
    // Create the part.
    PackagePart part = package.CreatePart(
        partNameUri, partType, compression);

    // Write the data from the file to the part.
    using (FileStream fileStream = File.OpenRead(partFile))
    {
        CopyStream(fileStream, part.GetStream()); // Copy data to part.
    }

    // Create a package-level relationship to the Web page part.
    package.CreateRelationship(
        part.Uri, TargetMode.Internal, packageRelationshipType);
}

Mithilfe von Beziehungen können Sie die in einem Paket gespeicherten Zuordnungen zwischen Inhalts- und Ressourcenelementen leicht auflösen und erkennen. In Abbildung 7 wird anhand eines Beispiels gezeigt, wie Sie ein Paket öffnen und dann mit der Paketbeziehung „root-html“ das Stamminhaltsteil im Paket finden. Das Stammteil kann dann abgefragt werden, um die Beziehungen zurückzugeben, die zusätzliche, vom Stammelement benötigte Ressourcen identifizieren.
using System.IO.Packaging;

// Path and name of the package file.
string packageFile = @”C:\webpages\packaging.htmx”; 

// Name for the root package relationship type.
string rootHtmlRelationshipType = “http://schemas.openxmlformats.org/” +  
    “package/2007/relationships/htmx/root-html”;

string resourceRelationshipType = “http://schemas.openxmlformats.org/” + 
    “package/2007/relationships/htmx/required-resource”;

// Open the package for reading. - - - - - - - - - - - - - - -
using (Package package =
    Package.Open(packageFile, FileMode.Open, FileAccess.Read))
{
    // A package can contain multiple root items, iterate
    // through each.  Get the “root-html” package relationship.
    foreach (PackageRelationship relationship in
        package.GetRelationshipsByType(rootHtmlRelationshipType))
    {
        // Get the part referenced by the relationship TargetUri.
        PackagePart rootPart = package.GetPart(relationship.TargetUri);

        // Open and access the part data stream.
        using (Stream dataStream = rootPart.GetStream())
        {
            ... //  Access the root part’s data stream.
        }

        // A part can have associations with other parts.
        // Locate and iterate through each associated part.
        // Iterate through each “required-resource” part.
        foreach (PackageRelationship resourceRelationship in
            rootPart.GetRelationshipsByType(
                resourceRelationshipType))
        {
            // Open the Resource Part and write the contents to a file.
            PackagePart resourcePart = package.GetPart(
                resourceRelationship.TargetUri);
            ... // Party with the resource part.
        }
    }
}


Digitale Paketsignaturen
Da ein Paket auf einer Komposition aus Teilen und Beziehungen aufbaut, kann es auch PackageDigitalSignature-Elemente (digitale Signaturen) enthalten. Eine digitale Signatur verwendet ein X.509-Zertifikat für das sichere Signieren von Teilen und Beziehungen in einem Paket. Die Signatur bietet zwei Features. Sie identifiziert und authentifiziert die Person oder Entität, die einen bestimmten Satz von Teilen und Beziehungen signiert hat. Außerdem wird sichergestellt, dass die signierten Teile und Beziehungen nicht geändert wurden.
Die digitale Signatur verhindert zwar nicht, dass ein Teil oder eine Beziehung geändert wird, jedoch schlägt eine Prüfung der Gültigkeit der Signatur fehl, falls das signierte Element verändert wurde. Die Anwendung kann dann eine entsprechende Aktion veranlassen. So kann z. B. verhindert werden, dass das Teil geöffnet wird, oder der Benutzer wird darüber benachrichtigt, dass die Daten nicht sicher sind.
Das Diagramm in Abbildung 8 zeigt ein Paket, das Teile und Beziehungen zusammen mit einer digitalen Signatur enthält, mit der bestimmte Elemente signiert wurden. Wenn eines der signierten Elemente in irgendeiner Weise geändert wird, schlägt die Überprüfung der digitalen Signatur fehl.
Abbildung 8 Signieren von Teilen und Beziehungen (Klicken Sie zum Vergrößern auf das Bild)
Weitere Informationen über digitale Paketsignaturen finden Sie im MSDN®-Artikel Das Digital Signing Framework der Open Packaging Conventions

Paketeigenschaften
In einem Paket kann auch ein Satz öffentlicher Metadaten als PackageProperties gespeichert werden. Dieser Satz umfasst 16 allgemeine Eigenschaften, die das Paket und seinen Inhalt beschreiben. Die Verwendung dieser Eigenschaften ist optional. In Abbildung 9 sehen Sie eine Übersicht über die für ein Paket verfügbaren Eigenschaften.

Eigenschaft Beschreibung
Category Eine Kategorisierung des Paketinhalts (Beispiel: Brief, Angebot, Lebenslauf)
ContentStatus Der Status des Paketinhalts (Beispiel: Entwurf, Geprüft, Endgültig).
ContentType Der Typ des Inhalts eines Pakets (Beispiel: Whitepaper, Bulletin).
Created Datum und Uhrzeit, zu dem/der das Paket erstellt wurde (siehe auch Modified).
Creator Der Name der Person oder Entität, die das Paket erstellt hat (siehe auch LastModifiedBy).
Description Eine Beschreibung des Paketinhalts.
Identifier Eine eindeutige Identifikation, die dem Paket und seinem Inhalt zugewiesen wurde.
Keywords Ein durch Trennzeichen getrennter Satz von Schlüsselwörtern zur Unterstützung der Suche und Indizierung.
Language Ein RFC 3066-Tag, das die Sprache identifiziert, für die der Paketinhalt geschrieben wurde.
LastModifiedBy Der Name der Person oder Entität, von der das Paket zuletzt geändert wurde (siehe auch Creator).
LastPrinted Datum und Uhrzeit, zu dem/der der Paketinhalt zuletzt gedruckt wurde.
Modified Datum und Uhrzeit, zu dem/der der Paketinhalt zuletzt geändert wurde (siehe auch Created).
Revision Die Revisionsnummer des Paketinhalts (siehe auch Version).
Subject Das Thema des Paketinhalts.
Title Der für den Paketinhalt angegebene Name.
Version Die Versionsnummer des Paketinhalts (siehe auch Revision).

Erstellen von Paketen und Teilen und Zugriff auf diese
Mit der Package.Open-Methode können Sie ein neues Paket erstellen oder ein vorhandenes Paket öffnen. Package.Open stellt Überladungen bereit, die Pakete entweder über einen angegebenen Dateinamen oder durch einen bestimmten Dateistrom instanziieren. Außerdem umfasst Package.Open Einstellungen, mit denen Sie Optionen für den Dateimodus (System.IO.FileMode) und Dateizugriff (System.IO.FileAccess) festlegen können.
Nachdem ein Paket instanziiert und geöffnet wurde, können Sie mit den Teilverwaltungsmethoden die im Paket enthaltenen Teile erstellen und darauf zugreifen (siehe Abbildung 10). Nach dem Abrufen einer Teilinstanz mit der CreatePart- oder der GetPart-Methode können Sie die GetStream-Methode für Teile aufrufen, um den Datenstrom zum Lesen oder Schreiben der Inhaltsdaten zurückzugeben. Wie Sie auf die Dateninhalte eines Teils zugreifen, werden wir später ausführlich erklären.

Paketmethode Beschreibung
CreatePart Erstellt ein neues Teil im Paket.
DeletePart Löscht ein angegebenes Teil aus dem Paket.
GetPart Gibt das Teil mit dem angegebenen Namen zurück.
GetParts Gibt eine Auflistung aller im Paket enthaltenen Teile zurück.
PartExists Bestimmt, ob ein bestimmtes Teil im Paket vorhanden ist.

Erstellen von Beziehungen und Zugriff auf diese
Bei Verwendung der System.IO.Packaging-APIs können Sie mit den CreateRelationship-Methoden der Package- und der PackagePart-Klasse Beziehungen auf Paketebene und auf Teilebene erstellen. Die CreateRelationship-Methoden verwenden fünf Elemente zum Definieren einer Beziehung:
  • Das Quellpaket oder das Quellteil
  • Einen URI für das Zielteil oder die externe Ressource
  • Ein targetMode-Element, das ermittelt, ob das Ziel im Paket enthalten (intern) oder nicht (extern) enthalten ist
  • Ein URI-formatiertes relationshipType-Element
  • Ein eindeutiges relationshipID-Element
Nachdem die Beziehungen erstellt wurden, können Sie über die Methoden „GetRelationshipsByType“, „GetRelationship“ oder „GetRelationships“ der Package- und der PackagePart-Klasse darauf zugreifen (siehe Abbildung 11). Im Abschnitt „Untersuchen eines Pakets“ gehen wir detaillierter darauf ein, wie Beziehungen in einem Paket gespeichert werden.

Paket-/Teilmethode Beschreibung
CreateRelationship Erstellt eine Beziehung auf Paketebene oder auf Teilebene zu einem angegebenen Ziel.
DeleteRelationship Löscht eine bestimmte Beziehung, deren Besitzer dieses Paket oder dieses Teil ist.
GetRelationshipsByType Gibt alle zu diesem Paket oder diesem Teil gehörenden Beziehungen zurück, die dem angegebenen Beziehungstyp (relationshipType) entsprechen.
GetRelationship Gibt die zu diesem Paket oder diesem Teil gehörende Beziehung zurück, die der angegebenen Beziehungs-ID entspricht.
GetRelationships Gibt alle Beziehungen zurück, deren Besitzer dieses Paket oder dieses Teil ist.
RelationshipExists Bestimmt, ob die angegebene Beziehung, deren Besitzer dieses Paket oder dieses Teil ist, vorhanden ist.

Paket-URI
Um auf ein Teil in einem Paket zuzugreifen, muss eine Adresse für das Paket und für das ausgewählte Teil angegeben werden. Ein Paket kann über konventionelle URI-Schemas wie http, ftp oder file identifiziert werden. Diese URI-Schemas bieten jedoch keine Methode für das Angeben eines einzelnen Teils im Paket. Auf Basis der erweiterbaren Architektur für URI-Schemas (RFC 3986) definiert der Open Packaging Conventions-Standard ein Paket-URI-Schema, mit dem einzelne Teile in einem Paket adressiert werden können.
Ein Paket-URI lässt sich ziemlich einfach erstellen. Mit einem konventionellen URI für eine Paketdatei und dem Namen eines im Paket enthaltenen Teils können Sie in zwei Schritten eine vollqualifizierte Paket-URI-Adresse erstellen, die dieses Teil identifiziert.
Angenommen, der URI lautet http://www.proseware.com/mypackage.docx, und der Teilname ist /images/chocolate.jpg. Der erste Schritt besteht darin, den URI des Pakets so zu codieren, dass er eine Paketautorität bildet. Dazu ersetzen Sie die Zeichen „?“, „@“, „:“, „%“ und „,“ durch ihre prozentcodierten Entsprechungen („?“ = „%3f“, „@“ = „%40“, „:“ = „% 3a“, „%“ = „%25“, „,“ = „% 2c“) und alle Schrägstriche (/) durch Kommas. Dann sieht der URI wie folgt aus:
http%3a,,www.proseware.com,mypackage.docx
Im zweiten Schritt kombinieren Sie das Schemapräfix „pack://“, die Paketautorität und den Teilnamen, um den Paket-URI für das angegebene Teil zu bilden. Das Ergebnis dieses Beispiels ist:
pack://http%3a,,www.proseware.com,mypackage.docx/images/chocolate.jpg
Bei anderen URI-Formen müssen zusätzliche Schritte ausgeführt werden, doch wenn Sie den hier beschriebenen grundlegenden Prozess verstehen, ist dies in den meisten Fällen ausreichend.
Es gibt einige wichtige Klassen, mit denen Sie vertraut sein sollten. Diese umfassen:
PackUriHelper Die PackUriHelper-Klasse stellt Methoden bereit, die das Erstellen von Paket-URIs sowie das Extrahieren und Zurückgeben des ursprünglichen Paket-URI oder des Teilnamens aus einem bestimmten Paket-URI vereinfachen.
PackWebRequest Um auf Daten über URIs zuzugreifen, können .NET-basierte Anwendungen die abstrakten Klassen „WebRequest“ und „WebResponse“ verwenden, aus denen schemaspezifische Unterklassen implementiert und registriert werden. Basierend auf dem Schema des URI, der für die WebRequest.Create-Methode angegeben wurde, wird automatisch die entsprechende Unterklasse ausgewählt und verwendet. Mit .NET Framework 3.0 bieten WebRequest und WebResponse direkte Unterstützung für Paket-URIs. Ein spezifisches Feature von Paket-URIs besteht darin, dass die von der GetResponseStream-Methode zurückgegebenen Datenströme vollständig durchsuchbar sind (falls die Suchfunktion vom Netzwerkprotokoll nicht unterstützt wird, werden Suchvorgänge auf Clientseite transparent durch die Paketimplementierung durchgeführt). Durch Verwendung von Paket-URIs kann Ihre Anwendung bei allen über URIs zugänglichen Datenquellen direkt auf beliebige Teile in einem Paket zugreifen.

Authentifizierung und Gültigkeitsprüfung
Paketdienste umfassen Unterstützung für digitale Signaturen, die zum Signieren und Überprüfen des Inhalts eines Pakets verwendet werden können. Zu den wichtigsten Methoden der PackageDigitalSignatureManager-Klasse gehören VerifyCertificate, Sign, VerifySignature und RemoveSignature.

Rechteverwaltung und Verschlüsselung
Über die von den Open Packaging Conventions definierten Funktionen hinaus bieten die System.IO.Packaging-APIs in .NET Framework 3.0 Unterstützung für die Gewährleistung des Datenschutzes und der Sicherheit im Hinblick auf die verpackten Inhalte. Bei Verwendung von Verschlüsselung und Rechteverwaltung in Verbindung mit einem Windows-Rechteverwaltungsserver können Pakete verschlüsselt werden, um den Zugriff auf bestimmte Personen oder Gruppen zu beschränken. Weitere Informationen zur Rechteverwaltung und Verschlüsselung finden Sie in den Beispielen „Rights Managed Package Publish“ und „Rights Managed Package Viewer“ von .NET Framework 3.0, die mit dem Windows SDK zur Verfügung gestellt werden.

Verpacken einer Webseite
Zur Veranschaulichung einiger grundlegender Vorgänge bei der Paketerstellung verwenden wir eine einfache Webseite mit lokalen Ressourcen. Beachten Sie jedoch, dass wir bei diesem Beispiel gewisse Einschränkungen in Kauf nehmen müssen, da Webseiten auf einem festen HTML-Schema basieren und wir Änderungen an den vorhandenen Seiteninhalten vermeiden möchten. Ein erweiterbares XML-basiertes Schema bietet größere Flexibilität für zusätzliche Paketerstellungsfeatures. Diese und weitere Optionen werden wir im Abschnitt zum Entwerfen eines Dateiformats besprechen.
Da nun die Grundlagen der Paketerstellung abgedeckt sind, werden wir ein Beispielprogramm, PackageHtmxWrite, zum Erstellen eines eigenen benutzerdefinierten HTMX-Pakets schreiben (der vollständige Quellcode für PackageHtmxWrite ist auf der MSDN Magazin-Website verfügbar). In diesem Paket wollen wir eine Webseite zusammen mit deren lokalen Stylesheets, Skripts und Bilddateiressourcen speichern. Um das erste Teil mit der HTML-Seite zu identifizieren, erstellen wir eine Beziehung auf Paketebene, die auf das im Paket gespeicherte HTML-Teil verweist. Wir erstellen auch Beziehungen auf Teilebene, die Zuordnungen zwischen der Webseite und jeder lokalen Ressource definieren, die im Paket gespeichert ist. Die Beziehungen machen die Zuordnungen zwischen den einzelnen Komponenten direkt erkennbar. Das Paket muss also nicht durchsucht werden, um die Webseite zu finden, und es ist keine Analyse des HTML-Inhalts erforderlich, um die zugehörigen Ressourcen zu finden.
Da die Webseite und ihre lokalen Ressourcen in der Regel durch relative Pfade definiert werden, können wir den relativen Pfad und den Dateinamen jedes Elements als Teilname zum Speichern des Datenstroms im Paket verwenden. Wir können beispielsweise mit dem HTML-Tag <img src="images/packaging-sign.png"> den Pfad und den Dateinamen des src-Attributs verwenden, um den URI-Namen „/images/packaging-sign.png“ zum Speichern des Bilds als ein Teil im Paket zu definieren. Die lokalen Ressourcen der Webseite sind im HTML-Code angegeben. Daher müssen wir den HTML-Code nur einmal analysieren, um alle Ressourcendateien zu identifizieren, die in der Seite verwendet werden.
Über den Befehl „File“ (Datei) | „Open“ (Öffnen) von PackageHtmxWrite kann der Benutzer eine zu ladende Webseite auswählen und im Webbrowser-Steuerelement anzeigen. Nachdem die Seite geladen wurde, kann der Benutzer über den Befehl „File“ (Datei) | „Save As“ (Speichern unter) den Speicherort und den Dateinamen angeben, um die Webseite und ihre lokalen Ressourcen in einer HTMX-Datei zu speichern. Mit dem Befehl „File“ (Datei) | „Save As“ (Speichern unter) werden Methoden aufgerufen, die den HTML-Code der Seite analysieren und eine Liste der lokalen Ressourcendateien erstellen, die im Paket enthalten sein sollen.
Die WritePackage-Methode führt dann eine Reihe von Schritten zum Erstellen des Pakets durch. Zuerst wird das Paket mit dem vom Benutzer angegebenen Pfad und Dateinamen erstellt. Dann wird für die HTML-Stammwebseite ein Teil zum Speichern der Stammseite erstellt. Dabei werden die HTML-Daten im Webseitenteil gespeichert und eine Beziehung auf Paketebene erstellt, die das Teil der HTML-Stammwebseite identifiziert. Für jede lokale Ressource, auf die in der Webseite verwiesen wird, erstellt die Methode ein Teil zum Speichern der Ressource, speichert die Daten der Ressource im Teil und erstellt eine Beziehung auf Teilebene von der Webseite aus, die das Ressourcenteil als Ziel hat. Der WritePackage-Code in Abbildung 12 veranschaulicht diesen Prozess.
private void WritePackage(
    string packageFilepath, string parsedHtml, Hashtable resourceHash)
{
    // Create and open the specified package file for writing.
    // (The ‘using’ statement ensures that the package is
    // closed and disposed when it goes out of scope.)
    using (Package package = Package.Open(
        packageFilepath, FileMode.Create))
    {
        string docPath = 
            webBrowser.Url.AbsoluteUri.Substring(rootPrefix.Length);
        string docPartName = Path.GetFileName(packageFilepath);
        docPartName = Path.ChangeExtension(docPartName, “htm”);

        if (!docPath.EndsWith(“/”))
        {
            // docPath is in the format of “/folder1/folder2/1.html”
            // Make it “/folder1/folder2/”
            docPath = docPath.Remove(docPath.LastIndexOf(‘/’)+1);
        }
        docPartName = docPath + docPartName;

        // The Web page and its resource files will likely be an absolute
        // path with a common folder prefix.  Determine the common folder
        // prefix to remove when the parts are stored.
        string commonFolderString =
            GetCommonFolderString(resourceHash.Values, docPartName);

        // Remove prefix of common folders from the Web page part name.
        docPartName = docPartName.Substring(commonFolderString.Length);

        // Create a Uri name for the Web page part.
        Uri webpagePartUri = PackUriHelper.CreatePartUri(
            new Uri(docPartName, UriKind.Relative));

        // Create a package part to store the Web page HTML.
        PackagePart webpagePart = package.CreatePart(webpagePartUri,
            System.Net.Mime.MediaTypeNames.Text.Html,
            CompressionOption.Normal);

        // Write the Web page HTML to the Web page part.
        using (StreamWriter sw = new StreamWriter(
            webpagePart.GetStream(), System.Text.Encoding.UTF8))
        {
            sw.Write(parsedHtml); // Write the Web page part.
        }

        // Create a package-level relationship to the Web page part.
        package.CreateRelationship(webpagePart.Uri,
            TargetMode.Internal, _packageRelationshipType);

        // Create and write each of the Web page’s local resource parts.
        foreach (Uri resourceUri in resourceHash.Keys)
        {
            string contenttype;
            // Open the stream for each resource file.
            using (Stream s = GetResourceStream(
                resourceUri, out contenttype))
            {
                // Nothing to do if the resource file has no stream.
                if (s == null) continue;

                // Get the package part name for the resource.
                ResourceInfo ri = (ResourceInfo)resourceHash[resourceUri];
                string partName = ri.partName;

                // Remove the leading common folders.
                partName = partName.Substring(commonFolderString.Length);

                // Create the part name URI for the resource.
                Uri partUri = PackUriHelper.CreatePartUri(
                    new Uri(partName, UriKind.Relative));

                // Use normal compression unless the data has already
                // been compressed or is an octet-stream.
                CompressionOption compression = CompressionOption.Normal;
                if (   contenttype.StartsWith(“image”)
                    || contenttype.StartsWith(“video”)
                    || contenttype.StartsWith(“audio”)
                    || contenttype.StartsWith(“application/octet-stream”))
                    compression = CompressionOption.NotCompressed;

                // Create the part for storing the resource file 
                // in the package.
                PackagePart resourcePart =
                    package.CreatePart(partUri, contenttype, compression);

                // Copy the data from the file to the package part.
                CopyStream(s, resourcePart.GetStream());

                // Create a part-level relationship from the Web page part
                // (owner) to the resource part (relationship target).
                webpagePart.CreateRelationship(partUri, 
                    TargetMode.Internal, resourceRelationshipType);
            }
        }
    }
}


Untersuchen eines Pakets
Bei der Ausführung des PackageHtmxWrite-Beispiels wird ein HTMX-Paket erstellt, das die ursprüngliche Webseite zusammen mit ihren lokalen Ressourcen enthält. Da dieses Paket eine ZIP-Datei ist, können wir die standardmäßige ZIP-Funktionalität von Windows verwenden, um die im Paket gespeicherten Komponenten zu prüfen. Abbildung 13 zeigt die Organisation der Teile des Beispiels „packaging.htmx“, das im Download zu diesem Artikel enthalten ist. Hier sehen Sie eine Übersicht über den Inhalt.
Abbildung 13 Organisation der Teile in Packaging.htmx (Klicken Sie zum Vergrößern auf das Bild)
In allen Paketen ist ein [Content_Types].xml-Teil gespeichert. Diese enthält eine Liste der MIME-Typen und -Erweiterungen für alle anderen Teile im Paket. In unserem Beispiel enthält [Content_Types].xml die folgenden Informationen:
<?xml version=”1.0” encoding=”utf-8” ?>
<Types xmlns=
    “http://schemas.openxmlformats.org/package/2006/content-types”>
  <Default Extension=”xml” ContentType=”text/xml” /> 
  <Default Extension=”htm” ContentType=”text/html” /> 
  <Default Extension=”html” ContentType=”text/html” /> 
  <Default Extension=”rels” ContentType=
    “application/vnd.openxmlformats-package.relationships+xml” /> 
  <Default Extension=”jpg” ContentType=”image/jpeg” /> 
  <Default Extension=”png” ContentType=”image/png” /> 
  <Default Extension=”css” ContentType=”text/css” /> 
</Types>
Innerhalb eines Pakets werden Beziehungen in XML-Teilen durch die Erweiterung „.rels“ definiert. Beziehungen auf Paketebene werden in einem Teil namens „/_rels/.rels“ definiert. Ein Paket kann eine beliebige Anzahl von Beziehungen auf Paketebene aufweisen. Die Beziehung mit dem Typ „/root-html“ wird im Teil „/_rels/.rels“ gespeichert. Dies ist die Beziehung, die wir erstellt haben, um den Teilnamen der HTML-Webseite zu identifizieren. Wir können diesen Beziehungstyp später in unseren Programmen verwenden, um den Teilnamen jeder gespeicherten HTML-Webseite zurückzugeben. In unserem Beispiel enthält /_rels/.rels den folgenden XML-Code:
<?xml version=”1.0” encoding=”utf-8” ?>  
<Relationships xmlns=
 “http://schemas.openxmlformats.org/package/2006/relationships”>
  <Relationship Type=
   “http://schemas.microsoft.com/opc/2007/relationships/htmx/root-html”
   Target=”/packaging.htm” TargetMode=”Internal” 
   Id=”Rf29a606b57094466” /> 
</Relationships>
Die HTM-Datei (oder HTML-Datei) ist eine eingebettete Kopie der HTML-Datei, die der Benutzer in der Anwendung ausgewählt hat.
In einem Paket werden Beziehungen auf Teilebene in einem Teil mit einem besonderen Namen gespeichert. Zuerst wird PartName des Quellteils in Pfad und Dateiname unterteilt. Der Beziehungsteilname wird dann durch Anhängen von _rels/ an den Pfad und von .rels an den Dateinamen gebildet. Wenn also PartName für die Quelle „/aaa/bbb/mypage.htm“ lautet, ist „/aaa/bbb/“ der Pfad und „mypage.htm“ der Dateiname. Der sich daraus ergebende Beziehungsteilname lautet dann „/aaa/bbb/_rels/mypage.htm.rels“.
Ein Teil kann beliebig viele Beziehungen haben. Im Paket „packaging.htmx“ definiert das Teil „packaging.htm“ seine Beziehungen im Teil „/_rels/packaging.htm.rels“.

Lesen eines Pakets
Bei der Beispielanwendung „PackageHtmxRead“ zu diesem Artikel wird ein Webbrowser-Steuerelement verwendet, um eine Webseite anzuzeigen, die in einer HTMX-Datei verpackt ist. Da der Webbrowser HTMX-Paketdateien nicht versteht, muss PackageHtmxRead zuerst die HTML-Datei und ihre Ressourcen in einem temporären lokalen Verzeichnis entzippen. Die OpenHtmxPackage-Methode in Abbildung 14 zeigt, wie der HTMX-Inhalt entzippt und dann die Webseite angezeigt wird.
public bool OpenHtmxPackage(string filepath)
{
    // Extract the Web page and its local resources to the temp folder.
    _tempFolder = GetTempFolder();

    // Create a new tempFolder directory.  If the tempFolder
    // exists, delete it and create a new empty one.
    DirectoryInfo directoryInfo = new DirectoryInfo(_tempFolder);
    if (directoryInfo.Exists) directoryInfo.Delete(true);

    // Extract the Web page and its local resources to the temp folder.
    _htmlFilepath = ExtractPackageParts(filepath, _tempFolder);

    // Check that the Web page has a valid path and filename.
    if (_htmlFilepath == null)
    {
        WritePrompt(“  Error: web page not found”);
        return false;
    }

    // Convert the path and filename to a URI for the browser control.
    Uri webpageUri;
    try
    {
        webpageUri = new Uri(_htmlFilepath, UriKind.Absolute);
    }
    catch (System.UriFormatException)
    {
        string msg = _htmlFilepath + “\n\nThe specified path and “ +
            “filename cannot be converted to a valid URI.\n\n”;
        System.Windows.MessageBox.Show(msg, “Invalid URI”,
            MessageBoxButton.OK, MessageBoxImage.Error);
        return false;
    }

    // Load the Web page.
    webBrowser.ScriptErrorsSuppressed = false;
    webBrowser.Url = webpageUri;
    return true;
}

Um die einzelnen Dateien zu extrahieren, ruft die OpenHtmxPackage-Methode die ExtractPackageParts-Methode auf, die das Paket nach der Beziehung abfragt, die das HTML-Stammteil identifiziert. Das HTML-Stammteil wird dann an den jeweiligen Speicherort im Zielverzeichnis kopiert.
Als Nächstes wird das HTML-Teil nach Beziehungen auf Teilebene abgefragt, die die zugeordneten Ressourcenteile identifizieren. Das Ressourcenteil für jede Beziehung auf Teilebene wird dann an den jeweiligen Speicherort im Zielverzeichnis kopiert. Auch hier spielt sich alles über die Beziehungen ab, und es müssen keine Teilinhalte analysiert werden! Der Beispielcode in Abbildung 15 zeigt die Methoden „ExtractPackageParts“ und „ExtractPart“, die zum Entzippen der HTML-Datei und ihrer lokalen Ressourcendateien verwendet werden.


ExtractPackageParts
private string ExtractPackageParts(
    string packageFile, string targetDirectory)
{
    Uri uriDocumentTarget = null;

    // Open the Package.
    using (Package package =
        Package.Open(packageFile, FileMode.Open, FileAccess.Read))
    {
        PackagePart documentPart = null,  resourcePart = null;

        // Examine the package-level relationships and look for
        // the relationship with the "root-html" RelationshipType.
        foreach (PackageRelationship relationship in
            package.GetRelationshipsByType(_packageRelationshipType))
        {
            // Resolve the relationship target URI so
            // the root-html document part can be retrieved.
            uriDocumentTarget = PackUriHelper.ResolvePartUri(
                new Uri("/", UriKind.Relative), relationship.TargetUri);

            // Open the document part and write its contents to a file.
            documentPart = package.GetPart(uriDocumentTarget);
            ExtractPart(documentPart, targetDirectory);
        }

        // Examine the root part’s part-level relationships and look
        // for relationships with "required-resource" RelationshipTypes.
        Uri uriResourceTarget = null;
        foreach (PackageRelationship relationship in
            documentPart.GetRelationshipsByType(
                resourceRelationshipType))
        {
            // Resolve the Relationship Target Uri so the resource part
            // can be retrieved.
            uriResourceTarget = PackUriHelper.ResolvePartUri(
                documentPart.Uri, relationship.TargetUri);

            // Open the resource part and write the contents to a file.
            resourcePart = package.GetPart(uriResourceTarget);
            ExtractPart(resourcePart, targetDirectory);
        }

    }

    // Return the path and filename to the file referenced
    // by the HTMX package’s "root-html" package-level relationship.
    return targetDirectory + uriDocumentTarget.ToString().TrimStart(‘/’);
}
...
private const string _packageRelationshipType =
    "http://schemas.openxmlformats.org/package/2007/" +
    "relationships/htmx/root-html";

private const string _resourceRelationshipType =
    "http://schemas.openxmlformats.org/package/2007/" +
    "relationships/htmx/required-resource";


ExtractPart
private static void ExtractPart(
    PackagePart packagePart, string targetDirectory)
{
    // Remove leading slash from the Part Uri and make a new
    // relative Uri from the result.
    string stringPart = packagePart.Uri.ToString().TrimStart(‘/’);
    Uri partUri = new Uri(stringPart, UriKind.Relative);

    // Create an absolute file URI by combining the target directory
    // with the relative part URI created from the part name.
    Uri uriFullFilePath = new Uri(
        new Uri(targetDirectory, UriKind.Absolute), partUri);

    // Create the necessary directories based on the full part path
    Directory.CreateDirectory(
        Path.GetDirectoryName(uriFullFilePath.LocalPath));

    // Write the file from the part’s content stream.
    using (FileStream fileStream = 
        File.Create(uriFullFilePath.LocalPath))
    {
        CopyStream(packagePart.GetStream(), fileStream);
    }
}

Signieren und Überprüfen eines Pakets
Das Beispielprogramm „PackageHtmxSign“ zeigt, wie digitale Signaturen verwendet werden können, um Teile und Beziehungen in einem Paket zu signieren. Mit einem X.509-Zertifikat können Sie die Entität identifizieren, die die Inhaltselemente signiert, und einen verschlüsselten Hash erstellen, der dann verwendet werden kann, um zu sicherzustellen, dass der Inhalt nicht geändert wurde.
Normalerweise würde eine Anwendung eine Richtlinie definieren, die die spezifischen zu signierenden Teile und Beziehungen auflistet. Um das Ganze für unser HTMX-Format einfach zu halten, gibt unsere Richtlinie vor, dass alle Teile und Beziehungen signiert werden sollen. Die SignAllParts-Methode zeigt, wie alle Teile und Beziehungen in einem Paket mit einer digitalen Signatur versehen werden. Währenddessen könnte mit der ValidateSignatures-Methode überprüft werden, ob keine Änderungen an den signierten Elementen vorgenommen wurden.

Entwerfen eines Dateiformats
Wenn Sie ein eigenes Paketdateiformat planen, sollten Sie verschiedene Punkte berücksichtigen. Zuerst sollten Sie mithilfe einer Beziehung auf Paketebene ein Anfangsteil im Paket festlegen. Obwohl Sie auch einen festen erforderlichen Teilnamen verwenden können, wird durch hartcodierte Teilnamen möglicherweise die Flexibilität Ihres Paketentwurfs eingeschränkt. Über Beziehungen und Beziehungstypen lassen sich Elemente auch ohne vordefinierte Teilnamen leicht auffinden.
Für Teile, die auf andere Ressourcen verweisen, sollten Sie die Erstellung einer Beziehung für jede Zielressource in Erwägung ziehen. Eine strukturierte Darstellung der Beziehungen und Beziehungstypen ermöglicht die einfache Erkennung der Elementstruktur im Paket, ohne dass der Inhalt jedes Teils analysiert werden muss.
In Fällen, in denen alle Ressourcen, auf die von einem Teil verwiesen wird, durch Beziehungen definiert sind, sollten Sie die Verweise im Teilinhalt durch die entsprechenden Beziehungs-ID-Werte ersetzen. Mithilfe von Beziehungs-IDs können Ressourcenverweise einfach durch Aktualisieren der entsprechenden Beziehung geändert werden, ohne den Inhalt des Teils selbst analysieren und ändern zu müssen. Beachten Sie, dass dies möglicherweise nicht angebracht ist, wenn ein Teil aus den Beziehungen, die auf die tatsächlichen Zielressourcen verweisen, extrahiert oder von ihnen getrennt wird.
Vermeiden Sie nach Möglichkeit relative Verweise auf Ressourcen außerhalb des Pakets. Wenn das Paket verschoben wird, werden die relativen Verweise auf externe Ressourcen wahrscheinlich zerstört.
Entscheiden Sie im Voraus, ob unbekannte Teile oder Beziehungen in Ihrem Paketformat zulässig sein sollen. Unbekannte Teile und Beziehungen können bei ihrem Auftreten einfach ignoriert werden. Bedenken Sie jedoch, dass Drittanbieter Ihrem Paket Elemente für unerwünschte Zwecke, beispielsweise zur Überwachung, hinzufügen könnten. Um die Sicherheit zu verbessern, sollte Ihre Anwendung unbekannte Teile oder Beziehungen als ungültige Formate betrachten (oder zumindest eine Warnung anzeigen). Wenn der Inhalt nicht geändert werden soll, sollten Sie ernsthaft das Signieren der Teile und Beziehungen in Ihrem Paket in Erwägung ziehen.


Jack Davis ist als Programmmanager bei Microsoft im Team „Windows Documents and Printing“ tätig. Früher arbeitete er als Programmierer und Autor mit dem Windows Presentation Foundation SDK-Team zusammen. Sie erreichen ihn unter jack.davis@microsoft.com.

Andrey Shurist als Programmmanager bei Microsoft im Team „Windows Documents and Printing“ tätig.

Page view tracker