Skip to content

Commit 35a9fbb

Browse files
authored
Add features collection to docs (#179)
1 parent 2e3a2b8 commit 35a9fbb

File tree

4 files changed

+182
-1
lines changed

4 files changed

+182
-1
lines changed

docs/general/features.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
---
2+
title: Custom SDK Features
3+
ms.suite: office
4+
5+
ms.author: o365devx
6+
author: o365devx
7+
ms.topic: conceptual
8+
ms.date: 11/13/2023
9+
ms.localizationpriority: medium
10+
---
11+
12+
# Custom SDK Features
13+
14+
Features in the Open XML SDK are available starting in v2.14.0 that allows for behavior and state to be contained within the document or part and customized without reimplementing the containing package or part. This is accessed via `Features` property on packages, parts, and elements.
15+
16+
This is an implementation of the [strategy pattern](https://refactoring.guru/design-patterns/strategy) that makes it easy to replace behavior on the fly. It is modeled after the [request features](/aspnet/core/fundamentals/request-features) in ASP.NET Core.
17+
18+
## Feature inheritance
19+
20+
Packages, parts, and elements all have their own feature collection. However, they will also inherit the containing part and package if it is available.
21+
22+
To highlight this, see the test case below:
23+
24+
```csharp
25+
OpenXmlPackage package = /* Create a package */;
26+
27+
var packageFeature = new PrivateFeature();
28+
package.Features.Set<PrivateFeature>(packageFeature);
29+
30+
var part = package.GetPartById("existingPart");
31+
Assert.Same(part.Features.GetRequired<PrivateFeature>(), package.Features.GetRequired<PrivateFeature>());
32+
33+
part.Features.Set<PrivateFeature>(new());
34+
Assert.NotSame(part.Features.GetRequired<PrivateFeature>(), package.Features.GetRequired<PrivateFeature>());
35+
36+
37+
private sealed class PrivateFeature
38+
{
39+
}
40+
```
41+
42+
> NOTE: The feature collection on elements is readonly. This is due to memory issues if it is made writeable. If this is needed, please engage on https://github.com/dotnet/open-xml-sdk to let us know your scenario.
43+
44+
## Visualizing Registered Features
45+
46+
The in-box implementations of the `IFeatureCollection` provide a helpful debug view so you can see what features are available and what their properties/fields are:
47+
48+
![Features Debug View](../media/feature-debug-view.png)
49+
50+
## Available Features
51+
52+
The features that are currently available are described below and at what scope they are available:
53+
54+
### IDisposableFeature
55+
56+
This feature allows for registering actions that need to run when a package or a part is destroyed or disposed:
57+
58+
```csharp
59+
OpenXmlPackage package = GetSomePackage();
60+
package.Features.Get<IDisposableFeature>().Register(() => /* Some action that is called when the package is disposed */);
61+
62+
OpenXmlPart part = GetSomePart();
63+
part.Features.Get<IDisposableFeature>().Register(() => /* Some action that is called when the part is removed or closed */);
64+
```
65+
66+
Packages and parts will have their own implementations of this feature. Elements will retrieve the feature for their containing part if available.
67+
68+
### IPackageEventsFeature
69+
70+
This feature allows getting event notifications of when a package is changed:
71+
72+
```csharp
73+
OpenXmlPackage package = GetSomePackage();
74+
package.TryAddPackageEventsFeature();
75+
76+
var feature = package.Features.GetRequired<IPackageEventsFeature>();
77+
```
78+
79+
> Note: There may be times when the package is changed but an event is not fired. Not all areas have been identified where it would make sense to raise an event. Please file an issue if you find one.
80+
81+
### IPartEventsFeature
82+
83+
This feature allows getting event notifications of when an event is being created. This is a feature that is added to the part or package:
84+
85+
```csharp
86+
OpenXmlPart part = GetSomePackage();
87+
package.AddPartEventsFeature();
88+
89+
var feature = part.Features.GetRequired<IPartEventsFeature>();
90+
```
91+
92+
Generally, assume that there may be a singleton implementation for the events and verify that the part is the correct part.
93+
94+
> Note: There may be times when the part is changed but an event is not fired. Not all areas have been identified where it would make sense to raise an event. Please file an issue if you find one.
95+
96+
### IPartRootEventsFeature
97+
98+
This feature allows getting event notifications of when a part root is being modified/loaded/created/etc. This is a feature that is added to the part level feature:
99+
100+
```csharp
101+
OpenXmlPart part = GetSomePart();
102+
part.AddPartRootEventsFeature();
103+
104+
var feature = part.Features.GetRequired<IPartRootEventsFeature>();
105+
```
106+
107+
Generally, assume that there may be a singleton implementation for the events and verify that the part is the correct part.
108+
109+
> Note: There may be times when the part root is changed but an event is not fired. Not all areas have been identified where it would make sense to raise an event. Please file an issue if you find one.
110+
111+
### IRandomNumberGeneratorFeature
112+
113+
This feature allows for a shared service to generate random numbers and fill an array.
114+
115+
### IParagraphIdGeneratorFeature
116+
117+
This feature allows for population and tracking of elements that contain paragraph ids. By default, this will ensure uniqueness of values and ensure that values that do exist are valid per the constraints of the standard. To use this feature:
118+
119+
```csharp
120+
WordprocessingDocument document = CreateWordDocument();
121+
document.TryAddParagraphIdFeature();
122+
123+
var part = doc.AddMainDocumentPart();
124+
var body = new Body();
125+
part.Document = new Document(body);
126+
127+
var p = new Paragraph();
128+
body.AddChild(p); // After adding p.ParagraphId will be set to a unique, valid value
129+
```
130+
131+
This feature can also be used to ensure uniqueness among multiple documents with a slight change:
132+
133+
```csharp
134+
using var doc1 = CreateDocument1();
135+
using var doc2 = CreateDocument2();
136+
137+
var shared = doc1
138+
.AddSharedParagraphIdFeature()
139+
.Add(doc2);
140+
141+
// Add item to doc1
142+
var part1 = doc1.AddMainDocumentPart();
143+
var body1 = new Body();
144+
var p1 = new Paragraph();
145+
part1.Document = new Document(body1);
146+
body1.AddChild(p1);
147+
148+
// Add item with same ID to doc2
149+
var part2 = doc2.AddMainDocumentPart();
150+
var body2 = new Body();
151+
var p2 = new Paragraph { ParagraphId = p1.ParagraphId };
152+
part2.Document = new Document(body2);
153+
body2.AddChild(p2);
154+
155+
// Assert
156+
Assert.NotEqual(p1.ParagraphId, p2.ParagraphId);
157+
Assert.Equal(2, shared.Count);
158+
```
159+
160+
### IPartRootXElementFeature
161+
162+
This feature allows operating on an `OpenXmlPart` by using XLinq features and directly manipulating `XElement` nodes.
163+
164+
```csharp
165+
OpenXmlPart part = GetSomePart();
166+
167+
var node = new(W.document, new XAttribute(XNamespace.Xmlns + "w", W.w),
168+
new XElement(W.body,
169+
new XElement(W.p,
170+
new XElement(W.r,
171+
new XElement(W.t, "Hello World!")))));
172+
173+
part.SetXElement(node);
174+
```
175+
176+
This `XElement` is cached but will be kept in sync with the underlying part if it were to change.

docs/general/overview.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@ ms.topic: conceptual
1414
ms.date: 11/01/2017
1515
ms.localizationpriority: high
1616
---
17+
1718
# Packages and general
1819

1920
This section provides how-to topics for working with documents and packages using the Open XML SDK.
2021

2122

2223
## In this section
2324

25+
- [Features in Open XML SDK](features.md)
26+
2427
- [Introduction to markup compatibility](introduction-to-markup-compatibility.md)
2528

2629
- [Add a new document part that receives a relationship ID to a package](how-to-add-a-new-document-part-that-receives-a-relationship-id-to-a-package.md)

docs/media/feature-debug-view.png

58.3 KB
Loading

docs/toc.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@
1515
- name: General
1616
items:
1717
- name: Overview
18-
href: general/overview.md
18+
href: general/overview.md
19+
- name: Custom SDK Features
20+
href: general/features.md
1921
- name: Introduction to markup compatibility
2022
href: general/introduction-to-markup-compatibility.md
2123
- name: Add a new document part that receives a relationship ID to a package

0 commit comments

Comments
 (0)