.NET, MAUI & Xamarin

C#beta

QRGen.Net for .NET: GS1/AAMVA/content parsers, a REST client, local encode/decode with ZXing.Net, and a .NET MAUI camera scanner page.

Edit on GitHub

Note: This page is generated from packages/dotnet/README.md. Source code: packages/dotnet.

Open-source (Apache-2.0) barcode, QR code and ID scanning for .NET. QRGen.Net gives you:

  • Parsers for GS1 element strings and Digital Link, AAMVA driver licenses, and QR payloads (URL, Wi-Fi, vCard/MECARD, events, payments, products…). Pure C#, no network, never throw.
  • QRGenClient, an HttpClient client for the QRGen REST API: 26 symbologies (including Micro QR, rMQR, MicroPDF417, DataBar Limited, Telepen, DX Film Edge) decoded and generated by any QRGen server.
  • QRGenLocal, offline encode/decode with ZXing.Net (SVG, PNG and raw pixels).
  • Embed helpers and a .NET MAUI scanner page that hosts the QRGen camera scanner in a WebView on Android, iOS, Mac Catalyst and Windows.

It follows the cross-platform QRGen specification: the same symbology ids, the same Barcode JSON, the same parser output as the JavaScript, Python, iOS, Android, Flutter and React Native packages.

Targets netstandard2.0 (so .NET Framework 4.6.2+ and older Xamarin-era projects) and net8.0.

Install

dotnet add package QRGen.Net

Until the package is on NuGet, build it from source:

git clone https://github.com/Rockyljewell/QR-GEN.git
cd QR-GEN/packages/dotnet
dotnet pack src/QRGen.Net -c Release -o ./nupkg
dotnet add <your project> package QRGen.Net --source ./nupkg
using QRGen;

Parsers

var gs1 = Gs1Parser.Parse("(01)09501101530003(17)250101(10)ABC123");
gs1["01"];                        // "09501101530003"
gs1.Elements[1].Title;            // "USE BY or EXPIRY"
gs1.Elements[1].Date;             // "2025-01-01"

Gs1Parser.Parse("]C1" + "0109501101530003" + "10ABC123\u001d" + "3103000195");   // raw + GS
Gs1Parser.Parse("https://id.gs1.org/01/09501101530003/10/ABC123?17=250101");      // Digital Link
Gs1Parser.ToHri(gs1);             // "(01)09501101530003(17)250101(10)ABC123"

var license = AamvaParser.Parse(pdf417Text, today: DateTime.Today);
license?.FullName;                // "JANE Q SAMPLE"
license?.DateOfBirth;             // "1990-01-31"
license?.IsUnder21;               // false

var content = ContentParser.Parse("WIFI:T:WPA;S:Home;P:secret;;");
content.Type;                     // "wifi"
content.Ssid;                     // "Home"
QRGenJson.Serialize(content);     // {"type":"wifi","ssid":"Home","password":"secret","security":"WPA","hidden":false}
  • ContentParser.Parse(data, symbology?, today?) returns a ParsedContent whose Type is one of url, gs1-digital-link, email, phone, sms, wifi, geo, contact, event, payment, product, gs1, aamva, text (constants in ParsedContentType). Only the properties of that type are set; QRGenJson omits the rest, so the JSON matches the other platforms field for field.
  • Gs1Parser knows 750+ AIs (fixed and variable lengths, dates with day 00 = last day of the month, decimal AIs as Number), and Gs1Parser.AiTable exposes the definitions.
  • AamvaParser handles AAMVA versions 1-10, legacy DAA/DCT names, US MMDDCCYY vs Canadian CCYYMMDD dates and ZIP+4, with an injectable today for Age, IsExpired and IsUnder21.

REST client

Run a QRGen server (qrgen serve from npm, qrgen-py serve from PyPI, or the Docker image), then:

using var client = new QRGenClient("http://localhost:8080");

await client.HealthAsync();                                   // { Ok = true, Version = "1.0.0" }

IReadOnlyList<Barcode> codes = await client.ScanAsync(File.ReadAllBytes("shelf.jpg"), new[] { "retail", "qr" });
foreach (var code in codes)
    Console.WriteLine($"{code.Symbology} {code.Data} -> {code.Parsed?.Type}");

byte[] png = await client.GenerateAsync("(01)09501101530003(10)ABC",
    new GenerateOptions { Symbology = "data-matrix", Format = "png", Gs1 = true, Scale = 6 });
string svg = await client.GenerateSvgAsync("https://example.com", new GenerateOptions { EcLevel = "H" });

ParsedContent parsed = await client.ParseAsync("MECARD:N:Doe,Jane;TEL:+15550100;;");
SymbologySupport support = await client.GetSymbologiesAsync();
  • ScanAsync accepts byte[] or a Stream, detects PNG/JPEG/GIF/BMP/WebP/TIFF from the bytes, and takes ScanOptions (Symbologies, TryHarder, MaxResults, ContentType).
  • Errors from the server ({ "error": { code, message } }) throw QRGenApiException with StatusCode, Code and Message.
  • Pass your own HttpClient (for IHttpClientFactory, auth headers, proxies): new QRGenClient(new Uri("https://scanner.internal/"), httpClient).

Local encode / decode (ZXing.Net)

string svg = QRGenLocal.Generate("https://example.com");                                   // SVG
string label = QRGenLocal.Generate("(01)09501101530003(10)ABC",
    new GenerateOptions { Symbology = "code128", Gs1 = true, Hrt = true, Scale = 3 });
byte[] png = QRGenLocal.GeneratePng("hello", new GenerateOptions { Scale = 8, Foreground = "#003366" });
BarcodeImage image = QRGenLocal.GenerateImage("hello");                                     // grayscale pixels

// Decode from a camera frame or bitmap you already have in memory
IReadOnlyList<Barcode> found = QRGenLocal.Decode(luminanceBytes, width, height, new[] { "qr", "ean13" });
IReadOnlyList<Barcode> fromBgra = QRGenLocal.Decode(bgraBytes, width, height, PixelFormat.Bgra32);
  • Readable locally: qr, data-matrix, aztec, pdf417, maxicode, ean13, ean8, upca, upce, isbn, code128, code39, code93, codabar, itf, itf14, databar, databar-expanded (QRGenLocal.ReadableSymbologies).
  • Writable locally: qr, data-matrix, aztec, pdf417, ean13, ean8, upca, upce, isbn, code128, code39, code93, codabar, itf, itf14 (QRGenLocal.WritableSymbologies). GS1 mode works for code128 and qr. For everything else (Micro QR, rMQR, MaxiCode output, DataBar output, GS1 Data Matrix…) use QRGenClient.
  • Results use the same Barcode shape: GS1 data in HRI form ((01)...), UPC-A as 12 digits, UPC-E as 8 digits, Codabar with start/stop characters. Location is the bounding box of ZXing’s result points (finder-pattern centers for 2D codes), so treat it as approximate.
  • PNG output comes from a small built-in encoder (PngEncoder), so there is no dependency on System.Drawing or SkiaSharp. Human readable text (Hrt) is rendered in SVG only.

JSON

QRGenJson.Options (camelCase, nulls omitted, relaxed escaping) produces the SPEC JSON:

string json = QRGenJson.Serialize(barcode);
// {"data":"https://example.com","symbology":"qr","symbologyName":"QR Code","rawBytes":"aHR0...","contentType":"text",
//  "isGS1":false,"location":{"topLeft":{"x":10,"y":10},...},"frameSize":{"width":1280,"height":720},"orientation":0,
//  "ecLevel":"M","symbologyIdentifier":"]Q1","timestamp":1735689600000}
Barcode? back = QRGenJson.Deserialize<Barcode>(json);

Barcode, TrackedBarcode, Quad, Point and ImageSize are records. Every property carries a [JsonPropertyName], so any JsonSerializerOptions gives the same names.

Symbologies

Symbologies.Resolve(new[] { "QRCode", "retail" });  // ["qr","ean13","ean8","upca","upce","isbn","databar",...]
Symbologies.Resolve("qr, ean-13");                  // ["qr","ean13"]
Symbologies.Get("GS1-128").Id;                      // "code128"
Symbology.DataMatrix.Name;                          // "Data Matrix"
Symbologies.Groups["industrial"];                   // code128 code39 code93 codabar itf itf14 data-matrix

Names are matched case-insensitively after removing everything except [a-z0-9]. Unknown names throw ArgumentException (or pass ignoreUnknown: true).

.NET MAUI camera scanner

src/QRGen.Maui is a sample library (copy it into your app or reference the project) with a QRGenScannerPage that hosts the hosted scanner page (https://rockyljewell.github.io/QR-GEN/embed/) in a WebView and receives results through the platform channel from SPEC section 5:

Platform WebView Channel used by the page
Windows WebView2 window.chrome.webview.postMessage(json)
iOS / Mac Catalyst WKWebView window.webkit.messageHandlers.qrgen.postMessage(json)
Android android.webkit.WebView window.QRGenAndroid.postMessage(json) (JavascriptInterface)
// MauiProgram.cs
builder.UseMauiApp<App>().UseQRGen();

// One-shot scan
var barcode = await QRGenScannerPage.ScanOnceAsync(Navigation,
    new EmbedOptions { Symbologies = { "qr", "ean13" }, Beep = true });

// Continuous / batch scanning
var page = new QRGenScannerPage(new EmbedOptions { Mode = "batch", Symbologies = { "retail" } });
page.Scanned += (s, e) => Debug.WriteLine(string.Join(", ", e.Barcodes.Select(b => b.Data)));
page.Tracked += (s, e) => Debug.WriteLine($"{e.Tracked.Count} codes in view");
page.ScannerError += (s, e) => Debug.WriteLine($"{e.Code}: {e.Message}");
await Navigation.PushAsync(page);
await page.SetTorchAsync(true);

EmbedOptions.BuildUri(), EmbedMessage.TryParse(json) and EmbedCommands (in QRGen.Net, namespace QRGen.Embed) work with any WebView, for example WPF or WinUI WebView2, if you do not use MAUI.

Camera permissions (the WebView can only open the camera after the app has the permission):

  • Android: AndroidManifest.xml needs <uses-permission android:name="android.permission.CAMERA" /> and <uses-feature android:name="android.hardware.camera" android:required="false" />. The handler requests the runtime permission and grants the page’s getUserMedia request in WebChromeClient.OnPermissionRequest.
  • iOS: Info.plist needs NSCameraUsageDescription. WKWebView supports camera access from iOS 14.3; the handler creates the WKWebView with inline media playback and grants the page’s capture request (iOS 15+).
  • Mac Catalyst: NSCameraUsageDescription plus the com.apple.security.device.camera entitlement.
  • Windows: add <DeviceCapability Name="webcam" /> to Package.appxmanifest; the handler allows WebView2’s camera permission request.

Xamarin

Xamarin and Xamarin.Forms reached end of support on May 1, 2024, so there is no Xamarin package. Move to .NET MAUI and use QRGenScannerPage, or keep your Xamarin app and use QRGen.Net itself (it targets netstandard2.0): the parsers, QRGenLocal and QRGenClient work there, and the embed page runs in any WebView through the channels listed above.

Building from source

cd packages/dotnet
dotnet build QRGen.Net.sln
dotnet test QRGen.Net.sln

The MAUI sample is not part of the solution; build it separately after dotnet workload install maui.

Status

Version 1.0.0.

  • Built and tested with the .NET 8 SDK on Linux: QRGen.Net for netstandard2.0 and net8.0, and 147 xUnit tests covering symbology resolution, the SPEC GS1 and AAMVA vectors and many parser cases, the SPEC section 2 JSON shape, QRGenLocal generate-then-decode round trips for 16 cases (plus PNG, SVG, pixel formats and errors), QRGenClient against a fake HTTP handler, and the embed helpers.
  • Checked by hand during development, not in the test suite: QRGenClient against the Python qrgen-py serve server (scan, generate, parse, errors), with parser JSON identical between the two; PNGs from QRGenLocal decoded by zxing-cpp.
  • The MAUI sample’s shared code compiles against Microsoft.Maui.Controls 8. The platform handlers (Android, iOS/Mac Catalyst, Windows) have not been compiled or run on devices; treat them as a documented starting point.
  • Not yet published to NuGet.

License

Apache-2.0. See LICENSE and NOTICE.

Created by Rockyljewell.

Esc

Share this page

.NET, MAUI & Xamarin · Docs | QRGen

Star QRGen on GitHub Add a "Powered by QRGen" badge