Write a Desktop app with React, Typescript, ASP.NET Core and WebView2
I was recently contacted by a client to rebuild a very old Microsoft Access application.
They didn't ask for any specific framework or platform to use so I was thinking to do it with WPF or the old good Windows Forms.
I have recently been working mainly on web applications with React as frontend and NodeJS or ASP.NET Core as backend and I found the development experience quite pleasant.
I search on the internet for a way to make a normal React/ASP.NET Core application as a Desktop one.
I found several libraries and frameworks but they all seem too heavy and I want a development experience very similar to the web one.
While I was searching I found a new technology done by Microsoft called WebView2 that is based on Chromium rendering engine:
https://docs.microsoft.com/en-us/microsoft-edge/webview2/
It's a quite remarkable technology.
If you bring together WebView2 on a Windows Forms application and spin an instance of ASP.NET Core with Kestrel, you get a full desktop application that can run like a web application.
Let's do it!
Create a new ASP.NET Core React application
You need to have installed on your machine NodeJS and ASP.NET Core
Let's create a new ASP.NET Core React app with the following command:
dotnet new react -o my-new-app
cd my-new-app
dotnet build
dotnet run
Now if we open the browser at https://localhost:5001/
We have the basic ASP.NET Core / React app.
Let's use Typescript
Now it's time to replace the React app using Javascript with another React app using Typescript. This will make our app more robust and easier to maintains.
Let's stop the server with CTRL+C and delete the folder ClientApp at the root of the app (my-new-app).
Now open the command prompt from the root of the app and run the following command:
npx create-react-app client-app --template typescript
Now rename the newly created folder client-app to ClientApp.
If you run it again you'll see the empty Create React App:
Let's make it a desktop application
Let's install WebView2 from a command prompt on the root of the app:
dotnet add package Microsoft.Web.WebView2
Now change my-new-app.csproj as in the following screenshot:
and create the following files at the root of the app:
frmMain.cs
using System.Windows.Forms;
namespace WinFormsApp
{
public partial class frmMain : Form
{
public frmMain()
{
InitializeComponent();
}
}
}
frmMain.Designer.cs
namespace WinFormsApp
{
partial class frmMain
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.webView21 = new Microsoft.Web.WebView2.WinForms.WebView2();
((System.ComponentModel.ISupportInitialize)(this.webView21)).BeginInit();
this.SuspendLayout();
//
// webView21
//
this.webView21.CreationProperties = null;
this.webView21.DefaultBackgroundColor = System.Drawing.Color.White;
this.webView21.Dock = System.Windows.Forms.DockStyle.Fill;
this.webView21.Location = new System.Drawing.Point(0, 0);
this.webView21.Name = "webView21";
this.webView21.Size = new System.Drawing.Size(1280, 768);
this.webView21.Source = new System.Uri("https://localhost:5001/", System.UriKind.Absolute);
this.webView21.TabIndex = 0;
this.webView21.ZoomFactor = 1D;
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 20F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1280, 768);
this.Controls.Add(this.webView21);
this.Name = "frmMain";
this.Text = "Desktop React App";
this.ShowIcon = false;
((System.ComponentModel.ISupportInitialize)(this.webView21)).EndInit();
this.ResumeLayout(false);
}
#endregion
private Microsoft.Web.WebView2.WinForms.WebView2 webView21;
}
}
frmMain.Designer.cs
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
Now append to Program.cs the following code:
Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmMain());
Like in the following screenshot:
Let's build our desktop React app
Now is time to see our basic app running as a desktop application.
From the command prompt run the following commands to build the app:
dotnet restore "./my-new-app.csproj" --runtime win-x64
dotnet publish "my-new-app.csproj" -c Release -o ./publish --no-restore --runtime win-x64 --self-contained true /p:PublishTrimmed=true /p:PublishSingleFile=true
This command will generate an executable of the application self-contained. It means you don't need to install dotnet core on the machine to run it.
Be aware it will take a bit of time the first time you build.
After it compiles you'll see a new folder publish and you can run my-new-app.exe:
Now we have a full ASP.NET Core/React/Typescript application running as a desktop application.
You can find the source code here:
https://github.com/megasoft78/asp-net-core-react-typescript-webview2
Time to create your killer app!
Good luck!
An amazing explanation.
is it possible to create a full app, with a logic enclosed in the backend and a database and be able to distribute it?
You can build a full app with the logic in the backend but you still need to have the frontend. Imagine this like a frontend/backend server run locally. You can embed the database if it’s something file based like SQLite but I’m not sure where to store to be able to write on it.
Hi, thank you very much for the explanation.
How do you distribute this ?
You can generate a self contained executable for the specific platform you are targeting:
https://learn.microsoft.com/en-us/dotnet/core/deploying/