Embedding Local Images using Data URIs

A consulting firm builds a client-report tool on top of the WinForms HTML Editor. Consultants assemble end-of-engagement reports: text, tables, screenshots of dashboards, diagrams exported from their analytics tool. When a report is finished, the tool saves the HTML into a single column of a SQL Server table -- one row per report, no companion files, no attachments table to keep in sync. A web app on the other side renders the HTML in the client portal.

The first version works for plain text reports. The first report with screenshots breaks instantly: the consultant authored it on her laptop with <img src="C:\Users\priya\Reports\acme-q3\chart-1.png">, the HTML lands in the database verbatim, and when the client portal renders it from a Linux web server, the images are gone. The screenshots only existed on Priya's laptop.

Making the HTML self-contained

The right answer for this shape of problem is to fold the image bytes directly into the HTML -- <img src="data:image/png;base64,iVBORw0K..."> instead of <img src="C:\...">. After the conversion, the HTML stands alone. Drop it in a database column, attach it to an email, write it to disk and copy it to another machine -- the images travel with it because they are part of it.

Same HTML before and after data URI conversion

The helper

On editor.Content (an IContentService):

void EmbedLocalImagesAsBase64();

The helper walks the document's <img> elements. For each one whose src is a local file path, it reads the bytes, infers the MIME type from the file extension (image/png, image/jpeg, image/gif, image/webp, etc.), Base64-encodes the bytes, and rewrites the src in place to the equivalent data: URI. Remote URLs (http://, https://) are left untouched -- the consultant can still embed a chart hosted on the firm's shared dashboard URL alongside the local screenshots.

The rewrite happens in the editor's in-memory document. After the call returns, editor.Content.GetBodyHtml(true) and editor.Content.GetDocumentHtml() reflect the new state. Read the HTML out of either of those.

The fixed save path

private async Task SaveReportAsync(Guid reportId)

{

    // 1. Fold local images into the HTML in place.

    //    After this call, every <img src="C:\..."> has become

    //    <img src="data:image/...;base64,...">.

    htmlEditor1.Content.EmbedLocalImagesAsBase64();



    // 2. Read the rewritten body HTML out of the editor.

    string body = htmlEditor1.Content.GetBodyHtml(getInXhtml: true);



    // 3. Persist. One column. One round trip.

    await _reports.UpdateBodyAsync(reportId, body);

}

Priya saves her Acme Q3 report. The client portal renders it correctly the next morning. The Linux web server never needs access to Priya's laptop.

Self-contained report rendered in the client portal

Refinement -- when not to use this

Base64 inflates the bytes by roughly 33 percent. Ten 500 KB screenshots become about 6.7 MB of HTML. The consulting firm's reports rarely have more than three or four screenshots, so a typical row stays well under a couple of megabytes -- comfortable for SQL Server, comfortable for the portal's page weight. A different team with a different shape of content -- say, a photography catalog application with dozens of high-resolution images per document -- would hit the limit fast. For those cases the right move is to keep the images as separate blobs and reassemble them at render time.

The mutation is also one-way at the markup level. After the rewrite, the original file paths are gone from the HTML. If Priya later opens the saved report and edits an image, she edits the data URI -- she does not edit the source PNG on her laptop. The consulting tool handles this by also storing the source files alongside the report, keyed by report ID, so the consultant can re-author from originals if needed. For tools where the source files are throwaway (screenshots taken once, never edited), the one-way conversion is fine on its own.

If the destination is an email message

The other shape of "embed local images" is sending email. For that case the editor ships a different helper, GetEmailMessageWithLocalImagesEmbedded(), which produces a proper multipart/related MailMessage with CID-referenced linked resources -- the format every email client expects. See Embedding Local Images for Email Clients. Using EmbedLocalImagesAsBase64() for an email body works in some clients but bloats the message by 33 percent and trips some spam filters; the CID approach is the proper one. Two helpers, two destinations.

Choosing between EmbedLocalImagesAsBase64 and GetEmailMessageWithLocalImagesEmbedded

Last updated on May 15, 2026