How to send/write and receive/read (BIDI) Serial Port Data from Blazor
Product JSPrintManager for Blazor Published 08/15/2025 Updated 08/15/2025 Author Neodynamic
Overview
JSPrintManager for Blazor supports Bidirectional (BIDI) Serial Port Communication from Blazor allowing you to Send/Write & Receive/Read data strings to any RS-232, COM & TTY port available on the client system.
In this walkthrough, you'll learn how to Send/Write & Receive/Read data strings from Blazor through any Serial port available at the client machine. This solution works with any popular browser like Chrome, Firefox, IE/Edge & Safari on Windows, Linux, Raspberry Pi and Mac systems!
Follow up these steps
- Be sure you install in your dev machine JSPrintManager (JSPM) (Available for Windows, Linux, Raspberry Pi & Mac)
This small app must be installed on each client that will print from your website! - In your Blazor project...
- Add a NuGet reference to the JSPrintManager Razor Component
- Add the JSPrintManager service...
- Add the following statement at the top of your
Startup
orProgram
file
using Neodynamic.Blazor;
- For Blazor Server
Add the following line in theStartup's ConfigureServices
method
services.AddJSPrintManager();
For .NET 8+ you must use Interactive Server components and render mode, so add these settings:
builder.Services.AddRazorComponents() .AddInteractiveServerComponents();
app.MapRazorComponents<App>() .AddInteractiveServerRenderMode()
- For Blazor WebAssembly
Add the following line in theProgram's Main
method
builder.Services.AddJSPrintManager();
- For Blazor Server
- Add the following statement at the top of your
- Add the following statement in the
_Imports.razor
file
@using Neodynamic.Blazor
- Add a new Razor Page and copy/paste the following code. Please read the source code comments to understand the printing logic!
For .NET 8+ Blazor Server you must use Interactive Server render mode, so add these settings:
@page "/" @rendermode InteractiveServer @inject JSPrintManager JSPrintManager
@page "/" @inject JSPrintManager JSPrintManager @using System @using System.Text <div> <strong>JSPM </strong><span>WebSocket Status </span> @if (JSPrintManager.Status == JSPMWSStatus.Open) { <span class="badge badge-success"> <i class="fa fa-check" /> Open </span> } else if (JSPrintManager.Status == JSPMWSStatus.Closed) { <span class="badge badge-danger"> <i class="fa fa-exclamation-circle" /> Closed! </span> <div> <strong>JSPrintManager (JSPM) App</strong> is not installed or not running! <a href="https://neodynamic.com/downloads/jspm" target="_blank">Download JSPM Client App...</a> </div> } else if (JSPrintManager.Status == JSPMWSStatus.Blocked) { <span class="badge badge-warning"> <i class="fa fa-times-circle" /> This Website is Blocked! </span> } else if (JSPrintManager.Status == JSPMWSStatus.WaitingForUserResponse) { <span class="badge badge-warning"> <i class="fa fa-user-circle" /> Waiting for user response... </span> } </div> @if (JSPrintManager.Status == JSPMWSStatus.Open) { <div class="row"> <div class="col-md-12"> <h2 class="text-center"> BIDI Serial Comm </h2> <hr /> </div> </div> @if (NumOfSerialPorts == -1) { <div class="row"> <div class="col-md-12"> <div class="text-center"> <div class="spinner-border text-info" role="status"> <span class="sr-only">Please wait...</span> </div> <strong><em>Getting serial ports...</em></strong> </div> </div> </div> } else if (NumOfSerialPorts == 0) { <div class="row"> <div class="col-md-12"> <div class="text-center"> <div class="alert alert-danger">No serial ports were detected on this system.</div> </div> </div> </div> } else { <div class="row"> <div class="col-md-12"> <EditForm Model="@MySerialComm"> <div class="form-group"> <div class="row"> <div class="col-md-2"> <label>Port:</label> <InputSelect @bind-Value="MySerialComm.PortName" class="form-control form-control-sm"> <option>Select a port...</option> @foreach (var s in JSPrintManager.SerialPorts) { <option value="@s">@s</option> } </InputSelect> </div> <div class="col-md-2"> <label>Baud Rate:</label> <InputNumber @bind-Value="MySerialComm.BaudRate" class="form-control form-control-sm" /> </div> <div class="col-md-2"> <label>Data Bits:</label> <InputSelect @bind-Value="MySerialComm.DataBits" class="form-control form-control-sm"> @foreach (var dbit in Enum.GetValues(typeof(SerialPortDataBits))) { <option value="@dbit">@dbit</option> } </InputSelect> </div> <div class="col-md-2"> <label>Parity:</label> <InputSelect @bind-Value="MySerialComm.Parity" class="form-control form-control-sm"> @foreach (var par in Enum.GetValues(typeof(SerialPortParity))) { <option value="@par">@par</option> } </InputSelect> </div> <div class="col-md-2"> <label>Stop Bits:</label> <InputSelect @bind-Value="MySerialComm.StopBits" class="form-control form-control-sm"> @foreach (var sbit in Enum.GetValues(typeof(SerialPortStopBits))) { <option value="@sbit">@sbit</option> } </InputSelect> </div> <div class="col-md-2"> <label>Flow Control:</label> <InputSelect @bind-Value="MySerialComm.FlowControl" class="form-control form-control-sm"> @foreach (var h in Enum.GetValues(typeof(SerialPortHandshake))) { <option value="@h">@h</option> } </InputSelect> </div> </div> <div class="row"> <div class="col-md-12"> <br /> <div class="text-center"> <button class="btn btn-success btn-lg" @onclick="DoSerialBidi"> Start BIDI Comm... </button> <button class="btn btn-danger btn-lg" @onclick="StopSerialBidi"> Stop </button> </div> </div> </div> <br /><br /> <div class="row"> <div class="col-md-12"> <br /> <strong>Data to Send:</strong> <div class="input-group mb-3"> <InputText @bind-Value="dataToSend" class="form-control form-control-sm" /> <InputSelect @bind-Value="endLineChars" class="form-control form-control-sm"> <option value="">None</option> <option value="CR">CR</option> <option value="LF">LF</option> <option value="CRLF">CRLF</option> </InputSelect> <div class="input-group-append"> <button class="btn btn-info" @onclick="DoSendData"> Send... </button> </div> </div> </div> </div> <br /><br /> <div class="row"> <div class="col-md-12"> <div class="text-center"> Serial Comm BIDI Messages<br /> <textarea class="terminal" readOnly @bind="Messages" rows="10" cols="100" /> </div> </div> </div> </div> </EditForm> </div> </div> } } @code { protected override void OnAfterRender(bool firstRender) { if (firstRender) { if (JSPrintManager.SerialPorts != null && JSPrintManager.SerialPorts.Length > 0) NumOfSerialPorts = JSPrintManager.SerialPorts.Length; // Handle OnGetSerialPorts event... JSPrintManager.OnGetSerialPorts += () => { NumOfSerialPorts = 0; if (JSPrintManager.SerialPorts != null && JSPrintManager.SerialPorts.Length > 0) NumOfSerialPorts = JSPrintManager.SerialPorts.Length; StateHasChanged(); }; JSPrintManager.OnStatusChanged += () => { StateHasChanged(); // Status = Open means that JSPM Client App is up and running! if (JSPrintManager.Status == JSPMWSStatus.Open) { //Try getting serial ports... JSPrintManager.TryGetSerialPorts(); } }; // Start WebSocket comm JSPrintManager.Start(); } base.OnAfterRender(firstRender); } protected override void OnInitialized() { JSPrintManager.OnError += () => { var error = JSPrintManager.LastErrorMessage; }; base.OnInitialized(); } private string Messages { get { return MessagesBuffer.ToString(); } set { MessagesBuffer.Clear(); MessagesBuffer.Append(value); } } private int NumOfSerialPorts = -1; private StringBuilder MessagesBuffer = new StringBuilder(); private string dataToSend = ""; private string endLineChars = ""; // The SerialComm info obj private SerialComm MySerialComm { get; set; } = new(); private void DoSerialBidi() { // handle BIDI Comm messages JSPrintManager.OnSerialCommEvent += () => { var evt = JSPrintManager.SerialCommEventCache[JSPrintManager.SerialCommEventCache.Count - 1]; switch (evt.Type) { case SerialCommEventType.Close: MessagesBuffer.Append("COMM CLOSE" + Environment.NewLine); break; case SerialCommEventType.Error: MessagesBuffer.Append("COMM ERROR: " + evt.Data + Environment.NewLine); break; case SerialCommEventType.Open: MessagesBuffer.Append("COMM OPEN" + Environment.NewLine); break; case SerialCommEventType.DataReceived: MessagesBuffer.Append("<< " + evt.Data); break; case SerialCommEventType.DataSent: MessagesBuffer.Append(">> " + evt.Data); break; } StateHasChanged(); }; // Start BIDI Serial Comm... JSPrintManager.OpenSerialComm(MySerialComm); } private void StopSerialBidi() { // Stop BIDI Serial Comm... JSPrintManager.CloseSerialComm(MySerialComm); } private void DoSendData() { // Send data through BIDI Serial Comm... if (MySerialComm.IsOpen && string.IsNullOrWhiteSpace(dataToSend) == false) { MySerialComm.DataToSend = dataToSend + endLineChars.Replace("CR", "\r").Replace("LF", "\n"); JSPrintManager.SendDataSerialComm(MySerialComm); } } }