.NET, MAUI & Xamarin
QRGen.Net for .NET: GS1/AAMVA/content parsers, a REST client, local encode/decode with ZXing.Net, and a .NET MAUI camera scanner page.
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, anHttpClientclient 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 aParsedContentwhoseTypeis one ofurl,gs1-digital-link,email,phone,sms,wifi,geo,contact,event,payment,product,gs1,aamva,text(constants inParsedContentType). Only the properties of that type are set;QRGenJsonomits the rest, so the JSON matches the other platforms field for field.Gs1Parserknows 750+ AIs (fixed and variable lengths, dates with day00= last day of the month, decimal AIs asNumber), andGs1Parser.AiTableexposes the definitions.AamvaParserhandles AAMVA versions 1-10, legacyDAA/DCTnames, USMMDDCCYYvs CanadianCCYYMMDDdates and ZIP+4, with an injectabletodayforAge,IsExpiredandIsUnder21.
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();
ScanAsyncacceptsbyte[]or aStream, detects PNG/JPEG/GIF/BMP/WebP/TIFF from the bytes, and takesScanOptions(Symbologies,TryHarder,MaxResults,ContentType).- Errors from the server (
{ "error": { code, message } }) throwQRGenApiExceptionwithStatusCode,CodeandMessage. - Pass your own
HttpClient(forIHttpClientFactory, 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…) useQRGenClient. - Results use the same
Barcodeshape: GS1 data in HRI form ((01)...), UPC-A as 12 digits, UPC-E as 8 digits, Codabar with start/stop characters.Locationis 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.xmlneeds<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’sgetUserMediarequest inWebChromeClient.OnPermissionRequest. - iOS:
Info.plistneedsNSCameraUsageDescription. 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:
NSCameraUsageDescriptionplus thecom.apple.security.device.cameraentitlement. - Windows: add
<DeviceCapability Name="webcam" />toPackage.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.Netfornetstandard2.0andnet8.0, and 147 xUnit tests covering symbology resolution, the SPEC GS1 and AAMVA vectors and many parser cases, the SPEC section 2 JSON shape,QRGenLocalgenerate-then-decode round trips for 16 cases (plus PNG, SVG, pixel formats and errors),QRGenClientagainst a fake HTTP handler, and the embed helpers. - Checked by hand during development, not in the test suite:
QRGenClientagainst the Pythonqrgen-py serveserver (scan, generate, parse, errors), with parser JSON identical between the two; PNGs fromQRGenLocaldecoded 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.