# Magicodes.IE, and the Excel engine it stopped wrapping

> Magicodes.IE imports and exports Excel, CSV, Word, PDF and HTML from C# across seventeen NuGet packages, and its newest package is a from-scratch streaming Excel engine written specifically to avoid depending on anyone else's. The package list is the architecture: two legacy Excel backends named after third-party libraries, five ABP integrations, and one new engine that does the job directly.

**dotnetcore/Magicodes.IE** — Import and export general library, support Dto import and export, template export, fancy export and dynamic export, support Excel, Csv, Word, Pdf and Html.

- Repository: https://github.com/dotnetcore/Magicodes.IE
- Website: http://docs.dotnet-china.com/Magicodes.IE
- Stars: 2,241 · Forks: 495
- Language: C#
- License: MIT
- Published: 2026-09-30 · Updated: 2026-09-30 · Language: en
- Canonical page: https://hysenlabs.com/projects/dotnetcore-magicodes-ie

## Four export strategies across five formats, which is a lot of axes

The project description is a single dense sentence and it contains the entire scope. It is an import and export general library, supporting DTO import and export, template export, fancy export and dynamic export, across Excel, CSV, Word, PDF and HTML.

That is two independent axes, and both of them multiply. Five formats times four strategies is twenty combinations, which is either a sign of thoroughness or a sign of a design that has accumulated every idea anyone had. The honest answer is probably both.

The four strategies are worth separating because they answer different questions. A DTO import and export is the ordinary case, where a list of typed objects becomes a spreadsheet and comes back. Template export is a different promise: you supply a document with placeholders, and the library fills them, so the output's layout is something a person designed rather than something code chose. Dynamic export is the opposite of typed, where the shape of the output is decided at runtime rather than by a class definition. And fancy export sits in the space between, and the README does not define it, which is the one term in that sentence a reader has to look up.

The formats are more familiar but the combination is the interesting part. CSV is trivial and always works. Excel is where the difficulty is, because the format is a zip archive of XML with styles, shared strings and formulas. Word is a different zip of XML with sections and styles. PDF is a page description language with font embedding, which is why the repository carries a `fonts` directory at the top level: to generate a PDF with a predictable typeface you have to bring the typeface. HTML is the easy one and is often the pragmatic answer to a request for a PDF.

So the library's value is not any single format. It is that one DTO definition and one set of conventions work across all five, so an application that exports a report can offer Excel for the analyst, PDF for the email and CSV for the integration without three different mapping layers.

## The package list is the architecture

The NuGet table in the README lists seventeen packages, and reading it as a diagram tells you more than any description would.

There is a core package, and then a package per format: Excel, Csv, Word, Pdf, Html. Those are the format implementations, and their existence is the reason the library is usable incrementally, because an application that only needs CSV should not take a dependency on a PDF engine.

Then there are the two that reveal the history. `Magicodes.IE.EPPlus` and `Magicodes.IE.Excel.NPOI` are both named after a third-party Excel library. EPPlus and NPOI are the two long-standing .NET options for reading and writing Excel, and having a package for each means the Excel support in this library historically ran over one of two backends and you chose. The README does not explain the difference between them, which is a genuine gap, but the naming convention makes the design unambiguous: the Excel path was a wrapper layer, and the wrapper layer had more than one engine.

Then there are the framework integrations. Five packages end in `.Abp`, covering Excel, Csv, Html, Pdf and Word, which means integration with the ABP Framework, a modular application framework for .NET. One integration per format, which is the shape of a first-class integration rather than an afterthought, and the reason is that ABP has its own conventions for module registration, dependency injection and configuration that a library wanting to be a first-class citizen in an ABP application has to honour.

Two more packages complete the picture. `Magicodes.IE.AspNetCore` and `Magicodes.IE.Excel.AspNetCore` are web framework integrations, and given that the new IO engine can write to a response body directly, the AspNetCore packages presumably predate that capability or cover the template and typed paths where an endpoint needs a different shape. `Magicodes.IE.Stash` is listed without explanation.

So the architecture is: a core with the conventions, a package per format, a package per legacy Excel backend, a package per ABP format, two web integrations and one package whose purpose is not documented. That is seventeen, and it is more packages than a small library should have. The mitigating factor is that the format packages are genuinely separable and the integration packages are genuinely opt-in, so a consumer takes one or two.

## Magicodes.IE.IO, and the decision to stop depending on other people's Excel code

The most important change in this project is a single package marked as new, and its description is a rejection of the project's own history. It installs with one command:

```bash
dotnet add package Magicodes.IE.IO
```

Magicodes.IE.IO is described as a zero-dependency, streaming, low-allocation Excel I/O engine, built from scratch without EPPlus, ClosedXML, or any third-party Excel library. It supports four target frameworks and has native async enumerable support and is described as AOT-friendly.

Read that as an engineering decision rather than a feature list. A library that wraps EPPlus inherits EPPlus's release cadence, EPPlus's breaking changes, EPPlus's licence and EPPlus's bug reports. When a dependency you do not control changes how it represents a cell or how it writes a shared string, your exported file changes shape, and the debugging session starts in a repository you have never read. Building the reader and writer yourself removes that entire category of problem and replaces it with a different one: you now own the format bugs.

The four properties in that description each solve a specific problem. Zero dependency is about supply chain and about not forcing a version constraint on your consumers. Streaming is about files larger than memory, and Excel exports are routinely larger than memory because a report with a million rows is one query away. Low allocation is the same problem from the other side, since a naive writer that builds a full object graph per cell will exhaust memory long before it exhausts disk. Native `IAsyncEnumerable` is what makes streaming composable, so a data source that produces rows lazily can feed the writer lazily. AOT-friendliness is about trimming: a framework that only works with runtime code generation cannot be trimmed, and trimming is what you need for a small self-contained deployment.

The target framework list is its own signal. `netstandard2.0` is there for older consumers, and then three successive .NET versions. A library that supports four target frameworks has to write to the lowest common denominator in places, so choosing to go that wide was a deliberate constraint rather than an accident.

None of this makes the legacy packages wrong. If you need the typed DTO mapping, the templates or the Word and PDF support, you still want the older stack. But for the specific job of getting rows of data into a valid xlsx file, the project now offers a path that does not go through anyone else's code.

## Writing to a response body, and what streaming actually changes

The four lines of example code for the new engine are short, and two of them change how you would write the surrounding application.

The first is a path, which is the ordinary case. The second writes to a response body directly, with no temporary file involved. The third produces a byte array in memory. The fourth is the interesting one, an async write that takes a stream and an async enumerable, with the example being a database query's async enumerable.

Look at what the fourth line removes. The conventional way to export a query result is to materialise it, map it, hand it to a library that builds a workbook, and write the workbook. At each step you hold a copy: a list of entities, a list of rows, an object graph, and finally a buffer. For a report that fits in memory that is wasteful but fine. For a report that does not, the first step is where it fails, and the fix used to be to page the query and concatenate the results, which means either holding all pages or writing multiple files and merging them.

Passing an async enumerable to the writer means the query and the write are one pipeline. Rows are read from the database as the writer consumes them, so peak memory is a function of the writer's buffer rather than of the result set. The database is also under less pressure, because a streaming query holds a cursor open rather than a sorted result, and on a large table the difference between a streaming read and a fully materialised one is the difference between a constant footprint and a table-sized allocation.

The second line, writing to a response body, removes a different thing. Exporting a spreadsheet from a web endpoint usually means building it in a temporary directory, then streaming that file to the client, then cleaning up, because the writer wants a seekable file and the response is a forward-only stream. A writer that accepts a response stream directly has no reason to want a file at all, which removes the temp file, the cleanup path, and the failure mode where the cleanup does not run.

Together these are the two capabilities that decide whether a library is usable for reporting at all, and they are the reason the engine was worth writing. The low-allocation claim matters for the same reason: an exporter that allocates per cell will throttle itself long before the database does.

## The one hard limitation in the README, and the tutorials that are not in the repository

There are three notes in the README under a heading that is not emphasised, and one of them is the most important fact on the page.

It says that Excel import does not support `.xls` files, that is, Excel 97-2003 is not supported. No elaboration, no workaround, no version of the legacy format that is tolerated. If your users send you workbooks from a system that has been exporting 97-2003 format since 2003, this library cannot read them, and you will find that out at the point where a customer tries to upload a file.

The other two notes are about process. One points at a section on using the library in Docker in the documentation, which tells you the library has an opinion about how it is deployed in a container, most likely around fonts and culture, both of which are common failures in a server-side document generator. The third says relevant functions have been compiled with unit tests and that you can refer to those tests while using the library, which is an unusual invitation and a good one, since a test file is the most honest documentation of a mapping library's actual behaviour.

The tutorial list is the other thing worth reading carefully. Eleven entries, and the distribution matters. Seven or eight of them are documents in the repository's own `docs` directory: importing student data, exporting Excel, exporting PDF receipts, using it in Docker, dynamic export, multi-sheet import, CSV import and export, importing and exporting Excel as pictures, and Excel template export for a textbook order form. Two are links to a blog on a personal documentation site, and one of those is a post from 2020.

So the documentation is mostly in the repository, partly in a blog, and part of it is four years old. The titles also reveal features that would not be obvious from the description: importing and exporting Excel as pictures, which means rendering cells as images rather than as cells, and a template export for a specific real business form, which is a strong hint about the project's origin as something built to solve one company's document problem.

That combination, a specific business form in the tutorial list, a Chinese translation at the top level, a community organisation and a personal blog for documentation, describes a library that grew out of one team's production problem and was then published. That is a common and generally good origin story, and it predicts the shape of the API.

## Community-hosted repository, personal CI, and a solution filter for seventeen packages

Three pieces of project infrastructure tell you how the project is actually run, and one of them is a risk.

First, the repository lives in the `dotnetcore` organisation, which the README identifies as the .NET Core Community, and the project's funding goes through an Open Collective. So the repository is community-hosted rather than under a vendor, which is a reasonable home for a library of this type and means no single company controls its direction.

Second, the continuous integration is not in that organisation. The build status badge points at an Azure Pipelines project under a personal DevOps organisation with a name that matches the author rather than the community. Both the test badge and the coverage badge come from that same personal organisation. So the repository is community infrastructure sitting on top of one person's build server, and anyone evaluating the project's health should note that the pipeline is a single point of failure that a repository move would not fix.

The badges also disagree in a small but useful way. The test badge is labelled for the master branch while the coverage badge is labelled for a branch. So the tests that run on the default branch and the coverage that gets reported are measuring different commits, which means a coverage percentage from this README does not describe master.

Third, the build files are the interesting part. There is both a solution file and a solution filter, which is the modern format for building a subset of a large multi-project solution. With seventeen packages, building everything on every local change is not something you want, and a filter lets a contributor open and build the two packages they are working on while the full solution remains available for a release. That is a small piece of pragmatism that shows up in a hundred-package repository and is usually missing from a ten-package one.

Alongside that, the package versions are centralised. There is a `Directory.Build.props` for build properties and a `Directory.Packages.props` for package versions, and the second of those is central package management: every NuGet version in the repository is declared in one file, so a transitive upgrade is a one-line change and a version skew between two of your own packages is impossible. There is also a `global.json` pinning the SDK, a `NuGet.Config`, a `RELEASE.md` describing the release process, and a `docfx.json` with a `toc.yml` for generated API documentation.

## Magicodes.IE against a typed data-table library and against a template engine

Two comparisons, and neither is close.

Against a data-table library, the usual approach is to serialise your entities to a grid and hand the grid to a formatter. The mapping is explicit, the shapes are limited by what the grid supports, and the output is uniform. Magicodes.IE is the same idea with conventions, and its advantage is that one entity definition serves five formats instead of one. If your requirement is a spreadsheet of a table, a data-table library is less machinery and will serve you fine.

The template export path is a genuinely different product and has no overlap with either of the others. When you export a textbook order form, the layout is a document a person designed with placeholders in particular places, and the code's job is to fill them. That is a document generation problem, not a serialisation problem, and the reason it appears in a library otherwise about data is that both are driven by the same annotations and the same conventions. If your requirement is a formatted document, this is the feature to evaluate and there is no substitute for it in a serialisation library.

The interesting comparison is with the format libraries it wraps. For Excel specifically, choosing this library's new IO engine means choosing a from-scratch implementation over EPPlus or ClosedXML, and the trade is as much about maintenance as features. A third-party library has a community, an issue tracker and a maintainer who will fix a malformed file that Excel complains about. A from-scratch engine in a smaller project has one author and a unit test suite. In exchange you get a dependency you control, a streaming path those libraries do not offer in the same shape, and a licence you have read.

For Word, PDF and HTML there is no equivalent escape hatch in this project, and the wrapping continues. That asymmetry is the honest summary of where the project is: the Excel path has been rebuilt and the others have not, and anyone whose requirement spans formats should check which of their formats are on the new path and which are still on a wrapper.

The ABP packages are worth one final word. If your application is built on that framework, the `.Abp` packages are what make this library a first-class participant rather than something you wire up by hand. If it is not, they are noise in the package list, and the count of seventeen is a reminder to install narrowly rather than reach for the metapackage.

## Conclusion

Adopt Magicodes.IE for a .NET application that has to produce or consume office documents from typed objects, and start with the new IO package if Excel is the only format you need, since it removes the third-party Excel dependency and streams. Do not choose it for a project that still receives Excel 97-2003 workbooks, because the README states .xls import is not supported, and check the licensing of the format libraries it still wraps for the other formats. Verify first by exporting a small query to a response body with Xlsx.Write and confirming the file opens, reading the Docker section in the documentation before deploying it in a container, and noting that the last tagged release is from 2021 while the IO package is tracked separately on NuGet.

## FAQ

### What is Magicodes.IE?

It is a C# import and export library supporting DTO import and export, template export, fancy export and dynamic export across Excel, CSV, Word, PDF and HTML. It is split into seventeen NuGet packages, with a core package, one package per format, ASP.NET Core integrations and ABP Framework integrations.

### What is Magicodes.IE.IO?

It is a new zero-dependency, streaming, low-allocation Excel I/O engine built from scratch without EPPlus, ClosedXML or any third-party Excel library. It supports netstandard2.0, net6.0, net8.0 and net10.0, with native IAsyncEnumerable support and is described as AOT-friendly, and it is installed with dotnet add package Magicodes.IE.IO.

### Does Magicodes.IE support old Excel files?

No. The README states plainly that Excel import does not support .xls files, that is, Excel 97-2003 is not supported. There is no stated workaround, so an application whose users still produce legacy workbooks needs to convert them before import.

### Can Magicodes.IE stream an export instead of buffering it?

Yes. The documented API includes writing straight to a response body, and an async write that takes an async enumerable, with the example being a database query, so a result set can be written without materialising it. The engine is described as streaming and low-allocation for exactly this case.

### Why does the package list contain both EPPlus and NPOI packages?

Because the Excel support historically ran over one of two third-party backends, and there is a package for each so a consumer could choose. The new IO package is the from-scratch replacement, and the README does not explain in detail how the three Excel paths differ.

### When was Magicodes.IE last released?

The most recent tags are v2.6.0 on 2021-11-28 and v2.5.6.3 on 2021-10-23, while the last push to the repository was on 2026-07-13. The version and download badges in the README track the Core and IO packages on NuGet rather than the repository tags, so the new IO package is versioned separately from the tagged releases.

## Sources

- [dotnetcore/Magicodes.IE on GitHub](https://github.com/dotnetcore/Magicodes.IE)
- [License: MIT](https://github.com/dotnetcore/Magicodes.IE/blob/master/LICENSE)
- [Project website](http://docs.dotnet-china.com/Magicodes.IE)
- [README](https://github.com/dotnetcore/Magicodes.IE/blob/master/README.md)
- [Releases](https://github.com/dotnetcore/Magicodes.IE/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dotnetcore-magicodes-ie
