Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a set of builder APIs that can handle shared state and creation #1541

Open
twsouthwick opened this issue Oct 17, 2023 · 1 comment
Open

Comments

@twsouthwick
Copy link
Member

Overview

Currently, there are a number of static APIs on each package type to enable creating based off of different sources. However, this has a couple of problems:

  • Makes a lot of code duplication to enable any new open related APIs
  • No easy way to create without knowing the package type (for scenarios where we just need a package)
  • Any initialization has to be duplicated and is not passed on to methods such as Clone()

Proposal

Expose a builder pattern with something like this that builds a middleware pipeline to construct the package:

public interface IPackageBuilder<TPackage>
    where TPackage : OpenXmlPackage
{
    /// <summary>
    /// Create an instance of the <typeparamref name="TPackage"/> package.
    /// </summary>
    /// <returns>A <typeparamref name="TPackage"/> instance.</returns>
    TPackage Create();

    /// <summary>
    /// Gets a key/value collection that can be used to share data between middleware.
    /// </summary>
    IDictionary<string, object?> Properties { get; }

    /// <summary>
    /// Add middleware to the package builder.
    /// </summary>
    /// <param name="configure">The middleware to add.</param>
    /// <returns>The <see cref="IPackageBuilder{TPackage}"/>.</returns>
    IPackageBuilder<TPackage> Use(Func<PackageInitializerDelegate<TPackage>, PackageInitializerDelegate<TPackage>> configure);

    /// <summary>
    /// Create a copy of the builder that will be independent of the original, but retains the existing middleware and properties.
    /// </summary>
    /// <returns>A new <see cref="IPackageBuilder{TPackage}"/>.</returns>
    IPackageBuilder<TPackage> Clone();

    /// <summary>
    /// Builds the pipeline to initialize the package. Additional calls to this will return the cached pipeline unless
    /// more middleware has been added.
    /// </summary>
    /// <returns>The pipeline to initialize a package.</returns>
    PackageInitializerDelegate<TPackage> Build();
}

This can enable something like the following:

var factory = WordprocessingDocument.CreateBuilder()
  .Use((package, next) => next(package))
  .UseSomeCommonBehavior()
  .Build();

using var package = factory.Open(stream);

This sets up a factory that will produce a package based on the supplied middleware, which can contain shared state, etc that is needed in each package. It also sets up things like Clone to use this as well so all packages are set up in a consistent way.

An initial implementation is available here: #1474

@jahav
Copy link

jahav commented Dec 4, 2024

FYI: Although the interface is currently internal, the name change of proposed (currently internal) API between PackageInitializerDelegate (3.1.1) -> PackageDelegate (3.2.0) likely caused TypeLoadException when combining SDK 3.1.1 and framework 3.2.0. Related issue: ClosedXML/ClosedXML#2560.

@AlfredHellstern AlfredHellstern modified the milestones: v3.1.0, v3.3.0 Dec 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants