<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Jordan</title>
    <description>The latest articles on DEV Community by Michael Jordan (@michael_jordan_87eaf96f24).</description>
    <link>https://dev.to/michael_jordan_87eaf96f24</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2760772%2Fc5bbe993-f5f7-480d-b9d7-9b6c4976965b.jpg</url>
      <title>DEV Community: Michael Jordan</title>
      <link>https://dev.to/michael_jordan_87eaf96f24</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/michael_jordan_87eaf96f24"/>
    <language>en</language>
    <item>
      <title>A Pure-C# PDF Toolkit for .NET: Read, Create, Edit, Optimize, Render, Fill Forms — and No Native Dependencies</title>
      <dc:creator>Michael Jordan</dc:creator>
      <pubDate>Wed, 24 Jun 2026 19:51:33 +0000</pubDate>
      <link>https://dev.to/michael_jordan_87eaf96f24/render-a-pdf-to-an-image-in-net-with-a-pure-c-pdf-engine-a7o</link>
      <guid>https://dev.to/michael_jordan_87eaf96f24/render-a-pdf-to-an-image-in-net-with-a-pure-c-pdf-engine-a7o</guid>
      <description>&lt;p&gt;Most PDF libraries in .NET are wrappers. PDFium, MuPDF, Ghostscript — solid engines, but they ship native binaries you P/Invoke into. That buys you per-RID packages, Native AOT friction, the "works on my box, throws &lt;code&gt;DllNotFoundException&lt;/code&gt; in the Alpine container" dance, and a deployment story that's never quite boring.&lt;/p&gt;

&lt;p&gt;PdfLibrary takes the other road: the entire PDF engine — parsing, content streams, fonts, and &lt;strong&gt;every image codec&lt;/strong&gt; (baseline and progressive JPEG, JPEG 2000, JBIG2, CCITT G3/G4 fax, LZW, Flate) — is pure managed C#. No vendored native PDF library, no native image decoders.&lt;/p&gt;

&lt;p&gt;The first version of this post had an honest asterisk: rendering a page to pixels went through SkiaSharp, which carries a native component, and I wasn't going to pretend otherwise. &lt;strong&gt;2.0 removed the asterisk.&lt;/strong&gt; Rendering is now a small geometry-only interface the core hands flattened paths to — bring your own target, or use the in-box WPF one, which is managed WPF and ships no native code. The packages you install now have zero native dependencies. More on that below.&lt;/p&gt;

&lt;p&gt;And rendering was always the flashy demo, not the point. So let me actually show you the toolkit.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;One core package — &lt;code&gt;Lxman.PdfLibrary&lt;/code&gt; — covers the whole document lifecycle, all managed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read &amp;amp; extract&lt;/strong&gt; — parse any PDF 1.x or 2.0; pull text, with positions if you want them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create&lt;/strong&gt; — build documents from scratch with a fluent builder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit&lt;/strong&gt; — add / remove / reorder / rotate pages, stamp, watermark, annotate, set metadata.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimize&lt;/strong&gt; — losslessly shrink: compression, object/xref streams, font subsetting, optional image recompression.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fill forms&lt;/strong&gt; — read and fill AcroForm fields, regenerate appearances, flatten.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pixels are the one thing that lives in a separate package, because most of the above never needs them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extract text
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfLibrary.Structure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;ExtractText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Edit a document
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfLibrary.Structure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Edit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;     &lt;span class="c1"&gt;// delete the 3rd page&lt;/span&gt;
&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Rotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// rotate the 1st page 90°&lt;/span&gt;
&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;      &lt;span class="c1"&gt;// move the 6th page to the front&lt;/span&gt;

&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"edited.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Optimize / shrink
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfLibrary.Optimization&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"optimized.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;PdfOptimizer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Optimize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// lossless by default&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Fill a form
&lt;/h2&gt;

&lt;p&gt;New and front-and-center in 2.0. Set field values, optionally flatten, save:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfLibrary.Structure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfLibrary.Editing.Forms&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"form.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;edit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Edit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;PdfTextField&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Forms&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"applicant.name"&lt;/span&gt;&lt;span class="p"&gt;]!).&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Ada Lovelace"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;PdfButtonField&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Forms&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"subscribe"&lt;/span&gt;&lt;span class="p"&gt;]!).&lt;/span&gt;&lt;span class="nf"&gt;Check&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Forms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Flatten&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;   &lt;span class="c1"&gt;// optional: bake the values into the page&lt;/span&gt;
&lt;span class="n"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"filled.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setting a value regenerates the field's appearance stream, so it renders correctly in any viewer afterward. (There's also a pure-WPF viewer in the repo that overlays &lt;em&gt;native&lt;/em&gt; controls on the form fields so a person can fill them by hand and hit Save — but that's a post of its own.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Render — and the "no native dependencies" story
&lt;/h2&gt;

&lt;p&gt;Here's the 2.0 change worth understanding. The core flattens everything — including text, down to glyph outlines — and calls a small interface, &lt;code&gt;IRenderTarget&lt;/code&gt;, with nothing but geometry: &lt;em&gt;fill this path, stroke this path, draw this image, set this clip.&lt;/em&gt; About seventeen methods, none of which require any PDF knowledge to implement. The hard part — operators, fonts, color, clipping — stays in the core.&lt;/p&gt;

&lt;p&gt;That makes rendering bring-your-own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;On Windows&lt;/strong&gt;, install &lt;code&gt;Lxman.PdfLibrary.Rendering.Wpf&lt;/code&gt; and render a page to a retained WPF &lt;code&gt;DrawingGroup&lt;/code&gt; — vector, so it's crisp at any zoom — then host it or rasterize it. Managed WPF, no native code.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;  &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;PdfLibrary.Rendering.Wpf&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PdfDocument&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"input.pdf"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="n"&gt;DrawingGroup&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;doc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;RenderToDrawing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// host `page` in an Image/DrawingImage, or render it into a&lt;/span&gt;
  &lt;span class="c1"&gt;// RenderTargetBitmap and PngBitmapEncoder it to a file.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anywhere else&lt;/strong&gt;, implement &lt;code&gt;IRenderTarget&lt;/code&gt; against a canvas you already have — SkiaSharp, ImageSharp, System.Drawing, a printer, an SVG writer, a game engine. The repo ships an SVG target as a worked example; it's small.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the honest breakdown, 2.0 edition:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parse, extract, create, edit, optimize, fill forms → &lt;strong&gt;fully managed, ship no native code.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Render to a vector visual or a bitmap on Windows → &lt;code&gt;Lxman.PdfLibrary.Rendering.Wpf&lt;/code&gt;, &lt;strong&gt;still no native code.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Render to a raster image cross-platform → &lt;strong&gt;you bring the canvas.&lt;/strong&gt; WPF won't help you on Linux; that's the one line to be precise about. But "implement ~17 geometry methods" is a very different proposition from bundling and P/Invoking a native PDF engine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No P/Invoke, no &lt;code&gt;SetDllDirectory&lt;/code&gt;, no &lt;code&gt;DllNotFoundException&lt;/code&gt; at 2am.&lt;/p&gt;

&lt;h2&gt;
  
  
  Threading
&lt;/h2&gt;

&lt;p&gt;PdfLibrary is built for the &lt;strong&gt;one-document-per-request&lt;/strong&gt; model — the standard ASP.NET Core pattern. Load a &lt;code&gt;PdfDocument&lt;/code&gt; per request, work with it, dispose it. Under that model it's thread-safe, and the process-wide caches (glyph paths, font resolution, ICC profiles) are synchronized. The one thing you must not do is share a single &lt;code&gt;PdfDocument&lt;/code&gt; across threads. (WPF rendering additionally wants an STA thread, as WPF always does.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Lxman.PdfLibrary
&lt;span class="c"&gt;# rendering on Windows (optional):&lt;/span&gt;
dotnet add package Lxman.PdfLibrary.Rendering.Wpf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NuGet: &lt;strong&gt;Lxman.PdfLibrary&lt;/strong&gt; · &lt;strong&gt;Lxman.PdfLibrary.Rendering.Wpf&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Source (MIT): &lt;strong&gt;github.com/lxman/PdfLibrary&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Targets .NET 8, 9, and 10. Current release: &lt;strong&gt;2.0.0&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try it and something doesn't behave the way you expect, open an issue with the PDF attached — edge cases in the wild are how this kind of library gets better.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>pdf</category>
      <category>opensource</category>
    </item>
    <item>
      <title>MongoDB-backed ASP.NET Core Identity, without the EF Core detour</title>
      <dc:creator>Michael Jordan</dc:creator>
      <pubDate>Wed, 24 Jun 2026 19:42:41 +0000</pubDate>
      <link>https://dev.to/michael_jordan_87eaf96f24/mongodb-backed-aspnet-core-identity-without-the-ef-core-detour-848</link>
      <guid>https://dev.to/michael_jordan_87eaf96f24/mongodb-backed-aspnet-core-identity-without-the-ef-core-detour-848</guid>
      <description>&lt;p&gt;If you're already running MongoDB and you reach for ASP.NET Core Identity, the&lt;br&gt;
official story points you at Entity Framework Core. That's a fine answer if you&lt;br&gt;
have a relational database. If you don't, you end up bolting a second data&lt;br&gt;
access stack onto an app that has exactly one store. You don't need it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;AspNetCoreIdentity.MongoDriver&lt;/code&gt; is a store provider that talks to Mongo through&lt;br&gt;
the official &lt;code&gt;MongoDB.Driver&lt;/code&gt; directly — no EF Core, no SQL, no second&lt;br&gt;
persistence model. It implements the Identity store interfaces, ships a&lt;br&gt;
&lt;code&gt;UserStore&lt;/code&gt; and &lt;code&gt;RoleStore&lt;/code&gt;, and wires up &lt;code&gt;UserManager&lt;/code&gt;/&lt;code&gt;RoleManager&lt;/code&gt; the way&lt;br&gt;
you already expect.&lt;/p&gt;
&lt;h2&gt;
  
  
  The 15-line version
&lt;/h2&gt;

&lt;p&gt;Register the provider in &lt;code&gt;Program.cs&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Services&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddIdentityMongoDbProvider&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MongoUser&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;MongoRole&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;identity&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequireUniqueEmail&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;mongo&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;mongo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetConnectionString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MongoDb"&lt;/span&gt;&lt;span class="p"&gt;)!;&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A connection string like &lt;code&gt;mongodb://localhost:27017/Identity&lt;/code&gt; creates an&lt;br&gt;
&lt;code&gt;Identity&lt;/code&gt; database and stores the collections there. That's the whole setup.&lt;br&gt;
Now inject the managers wherever you need them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UserManager&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;MongoUser&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;userManager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Controller&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They're registered as scoped services — let the container manage their&lt;br&gt;
lifetime. Don't call &lt;code&gt;BuildServiceProvider()&lt;/code&gt; yourself, and don't cache the&lt;br&gt;
managers in long-lived objects.&lt;/p&gt;
&lt;h2&gt;
  
  
  Your key type is yours
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;MongoUser&lt;/code&gt; and &lt;code&gt;MongoRole&lt;/code&gt; classes are generic, so the primary key isn't&lt;br&gt;
forced to be a &lt;code&gt;Guid&lt;/code&gt;. Want &lt;code&gt;string&lt;/code&gt; keys? Use &lt;code&gt;MongoUser&amp;lt;string&amp;gt;&lt;/code&gt;. If you use&lt;br&gt;
&lt;code&gt;string&lt;/code&gt; keys and don't assign an &lt;code&gt;Id&lt;/code&gt; on create, the store generates an&lt;br&gt;
ObjectId-style string for you.&lt;/p&gt;

&lt;p&gt;If you do go with &lt;code&gt;Guid&lt;/code&gt;, register the serializer once before the code above so&lt;br&gt;
Mongo stores them in the standard representation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;BsonSerializer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RegisterSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GuidSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GuidRepresentation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Standard&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Migrations that survive a rolling deploy
&lt;/h2&gt;

&lt;p&gt;Here's the part that usually bites people who hand-roll a Mongo store. On the&lt;br&gt;
first store operation — not during service registration — the library:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;applies any pending schema migrations, &lt;strong&gt;guarded by a distributed lock&lt;/strong&gt; so
that if several app instances boot at once, the migrations apply exactly
once; and&lt;/li&gt;
&lt;li&gt;creates its indexes: a unique index on &lt;code&gt;NormalizedUserName&lt;/code&gt;, an index on
&lt;code&gt;NormalizedEmail&lt;/code&gt;, a compound index on &lt;code&gt;Logins.LoginProvider&lt;/code&gt; /
&lt;code&gt;Logins.ProviderKey&lt;/code&gt;, and a unique index on the role &lt;code&gt;NormalizedName&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That distributed lock matters the moment you run more than one instance. Two&lt;br&gt;
pods starting simultaneously won't race each other into a half-applied schema.&lt;/p&gt;

&lt;p&gt;Because the &lt;code&gt;NormalizedUserName&lt;/code&gt; index enforces real uniqueness, index creation&lt;br&gt;
will fail if your existing data already contains duplicate user names — clean&lt;br&gt;
those up before you upgrade. If you'd rather own these concerns yourself, set&lt;br&gt;
&lt;code&gt;mongo.DisableIndexCreation = true&lt;/code&gt; and/or &lt;code&gt;mongo.DisableAutoMigrations = true&lt;/code&gt;.&lt;br&gt;
And if you want the work done at startup instead of on first request, resolve&lt;br&gt;
&lt;code&gt;MongoIdentityInitializer&lt;/code&gt; and await &lt;code&gt;EnsureInitializedAsync()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  It won't silently clobber concurrent writes
&lt;/h2&gt;

&lt;p&gt;Updates and deletes use optimistic concurrency through Identity's&lt;br&gt;
&lt;code&gt;ConcurrencyStamp&lt;/code&gt;. If the document changed since you loaded your copy, the&lt;br&gt;
operation returns a &lt;code&gt;ConcurrencyFailure&lt;/code&gt; instead of overwriting the other&lt;br&gt;
write. Reload, reapply, retry — the normal Identity dance.&lt;/p&gt;

&lt;h2&gt;
  
  
  One honest limitation
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;options.Stores.ProtectPersonalData&lt;/code&gt; is &lt;strong&gt;not&lt;/strong&gt; supported. The store doesn't&lt;br&gt;
encrypt personal data at rest, and rather than letting you flip that switch and&lt;br&gt;
quietly store unprotected data anyway, enabling it throws at runtime. If you&lt;br&gt;
need encryption-at-rest for PII, handle it at a different layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package AspNetCoreIdentity.MongoDriver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Package: &lt;a href="https://www.nuget.org/packages/AspNetCoreIdentity.MongoDriver" rel="noopener noreferrer"&gt;https://www.nuget.org/packages/AspNetCoreIdentity.MongoDriver&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source: &lt;a href="https://github.com/lxman/AspNetCoreIdentity.MongoDriver" rel="noopener noreferrer"&gt;https://github.com/lxman/AspNetCoreIdentity.MongoDriver&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're on Mongo and Identity, this is the short path. Issues and PRs welcome.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>csharp</category>
      <category>mongodb</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
