Click here to Skip to main content
15,901,122 members
Articles / Programming Languages / C#

C# in Browser via WebAssembly (without Blazor)

Rate me:
Please Sign up or sign in to vote.
5.00/5 (22 votes)
13 May 2024MIT14 min read 18.6K   40   18
In this article I describe how to write C# code that runs in Browser.
This article explains how to run .NET C# code in Browser via WebAssembly. Several example are given - describing calling C# from JavaScript, calling JavaScript from C# and running C# Program.Main(...) method from the browser. Finally I provide an Avalonia sample showing ho to run Avalonia visuals built in C# in the browser.

Image 1

Introduction

C# programs (as well as programs written in many other software languages) can now be run in almost any browser via WebAssembly - Wasm for short.

On top of being written in better languages, WebAssembly programs usually run faster than JavaScript/TypeScript programs, because they are compiled (at least to the intermediary language, some are even compiled to the native machine assembly language). Also as strongly typed languages they have considerably smaller and faster built-in types.

Avalonia

Avalonia is a great multiplatform framework for building UI applications. Avalonia can be used for building desktop applications for Windows, MacOS, Linux, Browser WebAssembly applications and mobile applications for iOS, Android and Tizen.

On top of everything else, Avalonia allows reusing most of the code between different platforms.

Here is a website demonstrating Avalonia controls built as Avalonia in-browser application: Avalonia Demo:

Image 2

In this article we shall talk only about in-browser Avalonia via Wasm.

Why "Without Blazor"

Initial Microsoft's WebAssembly supporting product was called Blazor (client side version) and was released as part of ASP.NET. Blazor can be used for doing non-visual operations and also to modify the HTML tree in C#.

However, I saw some complaints about Blazor stability and performance when it comes to interaction with HTML/JavaScript. Here is one place on the web mentioning Blazor stability and performance problems. I remember seeing similar complaints on other websites as well.

Because of those problems, this article focuses on using System.Runtime.InteropServices.JavaScript package which became part of .NET's WebAssembly SDK. This package is reported to provide better performance and more stable interactions with JavaScript.

The latest version of Avalonia also uses this library.

The Main Problem with WebAssembly - Lack of good Samples and good Documentation

While C# via Wasm is ready for prime time, the main problem is it being a relatively new technology it has very few good samples and good documentation available on-line.

The main purpose of this article is to provide easy to understand samples and good documentation covering all or most of C#-in-browser functionality.

Article Outline

Here are the topics this article covers:

  1. Creating Wasm projects and embedding them into browser code.
  2. Calling Browser JavaScript methods from Wasm C# and vice versa - calling Wasm C# methods from in-browser JavaScript.
  3. Calling C# Program.Main method from JavaScript. 
  4. Running Avalonia visual applications in browser.

Using ASP.NET Core for Samples

Browser based programming always implies a server - in all samples here I use ASP.NET due to the fact that ASP.NET is a great powerful, well tested, proven technology from Microsoft that works well with my favorite Visual Studio 2022 and allows keeping HTML/JavaScript client code together in the same project with the server code.

For the sake of speed and clarity I try avoiding ASP.NET code generation; instead I use ASP.NET as a Web and Data Server and the middle tier.

While ASP.NET is my choice for the server, exactly the same approach to deploying and running WebAssembly can be applied to any other server technology.

Samples Source Code Location

The samples' source code is located at Web Assembly Samples.

JavaScript calling C# Method Sample

Important Note

I'll go over the first sample with a great detail explaining almost everything with regards to the WebAssembly. This level of detail will not be maintained for the rest of the samples (since by that time you'll understand already how the WebAssembly works). So it is important to read this section, while the rest of the sample related sections you can read selectively depending on your needs.

Sample Location

This sample is located within JSCallingDotNetSample folder (its solution file has the same name).

Sample Code Overview

JSCallingDotNetSample solution has two projects in it:

  1. JSCallingDotNetSample - an ASP.NET 8.0 project. Please, make sure this is your start up project.
  2. Greeter - a C# .NET 8.0 library.

Image 3

How the Solution and the Projects were Created

In order to create the solution and the main ASP.NET project, I started Visual Studio 2022, clicked "Create a new project" option and chose "ASP.NET Core Web APP (Razor Pages)":

Image 4

Then I entered the name of the solution ("JSCallingDotNetSample") and made sure that the solution is created one directory above the project (and not in the same directory) by unselecting "Place solution and project in the same directory" check box.

Then to create the project containing C# code I right-clicked on the solution within the solution explorer, chose Add->New Project and then selected "Class Library" template:

Image 5

Note, that while in all samples, below, the ASP.NET projects are created in the same fashion, some C#-only projects will be created differently. Sometimes we would have to choose C# Console project (instead of class library) template and for Avalonia samples it will be even more fun and I'll give details about creating Avalonia Wasm projects below when we get to the topic.

Note that there are no project dependencies - the ASP.NET main project does not depend on the C# project. Though later I'll show how to introduce a build dependency between the projects.

Running the Project

In order to run the project successfully - first build the Greeter project. Under the project's bin/Debug/net8.0-browser/wwwroot folder a subfolder _framework will be created:

Image 6

Copy this _framework folder over under the wwwroot folder of the JSCollingDotNetSample ASP.NET project.

Image 7

Note that the folder should not become part of the source code (even though it has been moved under a source code folder). If you are using git you have to add this folder with its content to the git .ignore file (this is what a small red STOP icon before the folder means).

Build and run the main JSCallingDotNetSample project - first for a second or two you'll see a message "Please wait while Web Assembly is Loading!" and then it will be overridden by a message generated by C# code:

Image 8

C#-only Greeter Project

The C# Greeter project contains C# code called by JavaScript (from ASP.NET project). It has only one static class JSInteropCallsContainer and the class has a single method Greet that takes an array of strings (names) and returns a greeting string "Hello <name1>, <name2>, ... <name_N>!!!":

public static partial class JSInteropCallsContainer
{
    // this simple static method is exported to JavaScript
    // via WebAssembly
    [JSExport]
    public static string Greet(params string[] names)
    {
        var resultStr = string.Join(", ", names);

        // return a string greeting comma separated names passed to it
        // e.g. if the array of names contains two names "Joe" and "Jack"
        // then the resulting string will be "Hello Joe, Jack!!!".
        return $"Hello {resultStr}!!!";
    }
}  

Note that the class is static and partial and the method Greet(...) has JSExport attribute - this allows the method to be called from JavaScript.

Take a look at the project file - Greeter.csproj:

XML
<Project Sdk="Microsoft.NET.Sdk.WebAssembly">
    <PropertyGroup>
        <TargetFramework>net8.0-browser</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
        <OutputType>Library</OutputType>
    </PropertyGroup>
        ...
</Project> 

Note the highlighted differences from a usual C# Library project:

  1. The Sdk is set to Microsoft.NET.Sdk.WebAssembly.
  2. TargetFramework is set to net8.0-browser.
  3. <AllowUnsafeBlocks>true</AllowUnsafeBlocks>.

This three changes allow the project to produce (as a result of a build) _framework folder containing .wasm and other files needed for deployment.

JSCallingDotNetSample Code

Here I explain the changes made to the files within JSCallingDotNetSample ASP.NET project after creating it using "ASP.NET Core Web App (Razor Pages)" template.

Minor Modifications

For the sake of simplification, I removed the wwwroot/lib folder - since I do not plan to use either bootstrapper or jQuery.

I also greatly simplified the _Layout.cshtml file located under Pages/Shared folder, removing its footer, header and CSS classes.

Modifications to Program.cs File

I removed some unneeded lines from Progam.cs file and added Mime Types required by WebAssembly

C#
using Microsoft.AspNetCore.StaticFiles;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

// create a dictionary of mime types to add
var provider = new FileExtensionContentTypeProvider();
var dict = new Dictionary<string, string>
    {
        {".pdb" , "application/octet-stream" },
        {".blat", "application/octet-stream" },
        {".dll" , "application/octet-stream" },
        {".dat" , "application/octet-stream" },
        {".json", "application/json" },
        {".wasm", "application/wasm" },
        {".symbols", "application/octet-stream" }
    };

// add the dictionary entries to the provider
foreach (var kvp in dict)
{
    provider.Mappings[kvp.Key] = kvp.Value;
}

// set the provider to contain the added
// mime types
app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = provider
});

app.MapRazorPages();

app.Run();  

The mime types are added to a FileExtensionContentTypeProvider object which is then assigned to be the provider for the static files:

C#
var provider = new FileExtensionContentTypeProvider();

// create a dictionary of mime types to add
var dict = new Dictionary<string, string>
    {
        {".pdb" , "application/octet-stream" },
        {".blat", "application/octet-stream" },
        {".dll" , "application/octet-stream" },
        {".dat" , "application/octet-stream" },
        {".json", "application/json" },
        {".wasm", "application/wasm" },
        {".symbols", "application/octet-stream" }
    };

// add the dictionary entries to the provider
foreach (var kvp in dict)
{
    provider.Mappings[kvp.Key] = kvp.Value;
}

// set the provider to contain the added
// mime types
app.UseStaticFiles(new StaticFileOptions
{
    ContentTypeProvider = provider
});

Adding wasmRunner.js File

I added wasmRunner.js file to wwwroot folder (the same folder that contains copied _framework folder).

Here is the content of the file:

JavaScript
// note that it expects to load dotnet.js 
// (and wasm files) from _framework folder
import { dotnet } from './_framework/dotnet.js'

const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);

// get the objects needed to run exported C# code
const { getAssemblyExports, getConfig } =
    await dotnet.create();

// config contains the web-site configurations
const config = getConfig();

// exports contain the methods exported by C#
const exports = await getAssemblyExports(config.mainAssemblyName);

// we call the exported C# method Greeter.JSInteropCallsContainer.Greet
// passing to it an array of names
const text = exports.Greeter.JSInteropCallsContainer.Greet(['Nick', 'Joe', 'Bob']);

// logging the result of Greet method call
console.log(text);

// adding the result of Greet method call to the inner text of 
// an element out id "out"
document.getElementById("out").innerText = text;  

The file is well documented in-code. Here are the most important lines from that file:

  1. We load the dotnet object from ./_framework/dotnet.js file.
    JavaScript
    import { dotnet } from './_framework/dotnet.js'  
  2. We create two methods from dotnet - one returns an object containing all the C# exports to JavaScript and the other returns configuration for the WebAssembly project and the web site.
    JavaScript
    const { getAssemblyExports, getConfig } = await dotnet.create();        
  3. We call getConfig() method to get the config config object:
    const config = getConfig();  
  4. We use getAssemblyExport(...) method to get the export object containing all C# exports:
    JavaScript
    const exports = await getAssemblyExports(config.mainAssemblyName);
  5. We call the exported C# Greeter.JSInteropCallsContainer.Greet(...) method and save the result in variable text:
    const text = exports.Greeter.JSInteropCallsContainer.Greet(['Nick', 'Joe', 'Bob']);        
  6. Finally we assign the obtained text to be the inner text of our div element with id="out":
    document.getElementById("out").innerText = text;        

Modifying Index.cshtml File

The last file that I change is Pages/Index.cshtml file. I changed its content to be:

HTML
<div id="out"
     style="font-size:50px">
    <div style="font-size:30px">
        Please wait while Web Assembly is Loading!
    </div>
</div>
<script type="module" src="~/wasmRunner.js"></script>  

Note that it has a div element with id equals to "out", whose content will be replaced with the text.

Also note that it loads the module wamsRunner.js from wwwroot folder (this is what ~/ means).

Improving Performance of in-Browser C#

The in-browser performance of C# code can be improved in many ways. Most important is compiling C# AOT (ahead of time). This might increase the size of .wasm files but will greatly improve the performance.

Our sample demonstrates how to use AOT compilation in Release configuration.

You can create release AOT version of Greeter by going to the Greeter project folder on the command line and executing the following command:

dotnet publish -c Release  

It will create the _framework folder under bin\Release\net8.0-browser\publish\wwwroot folder.

This folder _framework will contain the optimized version of .wasm files. Move or copy this folder under JSCallingDotNetSample/wwwroot folder in exactly the same fashion as before and now when you run the project it will load the optimized .wasm files.

Important Note: AOT compilation of projects can take a lot of time - even 10-15 minutes if the project is large enough. In our case, however, our project is very small and the AOT building should take only a couple of seconds.

Take another look at Greeter.csproj project file. The AOT instructions are contained within a PropertyGroup conditioned on Release Configuration:

XML
<PropertyGroup Condition="'$(Configuration)'=='Release'">
	<RunAOTCompilation>true</RunAOTCompilation>
</PropertyGroup>  

Creating Build Dependency and Copying _framework Folder using a Post Build Event

Note that in Debug configuration we can set the project Greeter to be build before the main JSCallingDotNetSample project without setting the project dependency.

In order to do it right, click on the solution JSCallingDotNetSample in the solution explorer and choose "Project Dependencies..." menu item:

Image 9

In the open dialog choose JSColldingDotNetSample under Projects and within "Depends on" panel made sure the checkbox is checked. Then press "OK" button.

Image 10

This will ensure that Greeter project builds before the main ASP.NET JSCallingDotNetSample project.

Now to copy the _framework folder automatically under Debug mode we add the following lines to the end of JSColldingDotNetSample.csproj file:

XML
<Project Sdk="Microsoft.NET.Sdk.Web">
    ...
    <Target Condition="'$(Configuration)'=='Debug'"
            Name="PostBuild"
            AfterTargets="PostBuildEvent">
        <Exec Command="xcopy "$(SolutionDir)Greeter\bin\$(Configuration)\net8.0-browser\wwwroot\_framework" "$(ProjectDir)wwwroot\_framework" /E /R /Y /I" />
    </Target>
</Project>  

Note, that this can be done only for the Debug option, because Release would require a publish step on Greeter project. Of course it should be possible to automate it also, but at this point I do not want to spend time figuring it out.

Example of C# Calling JavaScript Program

This sample is located within DotNetCallingJSSample folder (its solution has the same name as the folder). It is built on top of the previous sample.

It modifies our exported method

C#
[JSExport]
public static string Greet(params string[] names)
{
   ...
}  

to depend on another method

C#
[JSImport("getGreetingWord", "CSharpMethodsJSImplementationsModule")]
public static partial string GetGreetingWord();  

whose implementation is provided within JavaScript.

JSInteropCallsContainer class within Greeter project has two methods instead of one.

Greeter.GetGreetingWord() is an extra method that's not implemented. It is marked as partial and it has JSImport("getGreetingWord", "CSharpMethodsJSImplementationsModule") attribute:

C#
public static partial class JSInteropCallsContainer
{
    [JSImport("getGreetingWord", "CSharpMethodsJSImplementationsModule")]
    public static partial string GetGreetingWord();
    ...
}

The attribute parameters signify that the program expects GetGreetingWord() method to be implemented by JavaScript getGreetingWord() method within JavaScript module named "CSharpMethodsJSImplementationsModule". A bit of a forward reference, but not the end of the world.

The method Greet(params string[] names) has been slightly modified to get the greeting word from GetGreetingWord() method instead of it being hardcode to "Hello":

C#
public static partial class JSInteropCallsContainer
{
    [JSImport("getGreetingWord", "CSharpMethodsJSImplementationsModule")]
    public static partial string GetGreetingWord();

    // this simple static method is exported to JavaScript
    // via WebAssembly
    [JSExport]
    public static string Greet(params string[] names)
    {
        var resultStr = string.Join(", ", names);

        // return a string greeting comma separated names passed to it
        // e.g. if the array of names contains two names "Joe" and "Jack"
        // then the resulting string will be "Hello Joe, Jack!!!".
        return $"{GetGreetingWord()} {resultStr}!!!";
    }
}    

The only file changed within main DotNetCallingJSSample project is wwwroot/wasmRunner.js. It has one line modified and one method call inserted:

JavaScript
// get the objects needed to run exported C# code
const { getAssemblyExports, getConfig, setModuleImports } =
    await dotnet.create();


// we set the module import
setModuleImports("CSharpMethodsJSImplementationsModule", {
    getGreetingWord: () => { return "Hi"; }
});

Note that on top of the methods getAssemblyExport(...) and getConfig(...) (that we already used in the previous sample) we also obtain method setModuleImports(...) from await dotnet.create() call.

We then use setModuleImports(...) method to set up getGreetingWord() method within "CSharpMethodsJSImplementationsModule" module to always return "Hi".

Now, rebuild the main project DotNetCallingJSSample (to force also the rebuilding Greeter project and copying _framework folder) and run it. We shall see "Hi Nick, Joe, Bob!!!" - the greeting is "Hi" instead of "Hello":

Image 11

Running C# Main Method in Web Assembly Sample

Next I'll show how run a C# Program.Main method from JavaScript. The corresponding sample is located under JSCallingCSharpMainMethodSample/JSCallingCSharpMainMethodSample.sln solution.

First of all rebuild and try running the main project JSCallingCSharpMainMethodSample. Press F12 to open the devtools in your browser. Click on the Console tab. You will see whatever is printed to the console:

Image 12

Line "Welcome to WebAssembly Program.Main(string[] args)!!!", then line "Here are the arguments passed to Program.Main:" and finally "arg1", "arg2" and "arg3" are each printed on its own line.

The Greeter project in that solution has only one simple file C# Program.cs:

C#
namespace Greeter;

public static partial class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("Welcome to WebAssembly Program.Main(string[] args)!!!");

        if (args.Length > 0)
        {
            Console.WriteLine();
            Console.WriteLine("Here are the arguments passed to Program.Main:");

            foreach(string arg in args) 
            { 
                Console.WriteLine($"\t{arg}");
            }
        }
    }
}  

It will print to console "Welcome to WebAssembly Program.Main(string[] args)!!!" and then if there are some arguments passed to the main, it will aso print the line "Here are the arguments passed to Program.Main:" and then it will print each argument on its own line.

Now look at Greeter.csproj file:

XML
<Project Sdk="Microsoft.NET.Sdk.WebAssembly">

    <PropertyGroup>
        <TargetFramework>net8.0-browser</TargetFramework>
        <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
        <OutputType>Exe</OutputType>
        <StartupObject>Greeter.Program</StartupObject>
    </PropertyGroup>

    ...
</Project>  

Note - we added <RuntimeIdentifier>browser-wasm</RuntimeIdentifier>, we changed the OutputType to "Exe" and we added the StartupObject line

Within JSCallingCSharpMainMethodSample (main) project the only file changed is wasmRunner.js:

JavaScript
// note that it expects to load dotnet.js 
// (and wasm files) from _framework folder
import { dotnet } from './_framework/dotnet.js'

const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);

// get the dotnetRuntime containing all the methods
// and objects for invoking C# from JavaScript and
// vice versa.
const dotnetRuntime = await dotnet.create();

// config contains the web-site configurations
const config = dotnetRuntime.getConfig();

// call Program.Main(string[] args) method from JavaScript
// passing to it an array of arguments "arg1", "arg2" and "arg3"

We get dotnetRuntime from await dotnet.create() and then call its dotnetRuntime.runMain(...) method to call the Program.Main(...) method of C#:

JavaScript
...
const dotnetRuntime = await dotnet.create();
...
await dotnetRuntime.runMain(config.mainAssemblyName, ["arg1", "arg2", "arg3"]); 

Note that the second argument passed to dotnetRuntime.runMain(...) should be an array of strings. These are the strings that will be passed over to Program.Main(string[] args) as args. This is why the program printed "arg1", "arg2" and "arg3" to the console. If you change the arguments within that array you shall see the corresponding changes in the program's output.

Run Avalonia in Browser via WebAssembly

Small Intro to Avalonia in Browser

Avalonia can run on many platforms including in-browser via WebAssembly.

Avalonia in Browser sample project is located under AvaInBrowserSample/AvaInBrowserSample.sln solution.

Open the solution, and make AvaInBrowserSample ASP.NET project to be your startup project.

Running the Project

Try rebuilding AvaInBrowserSample project and then run it in the debugger. After a couple of seconds Avalonia application is going to appear in the browser:

Image 13

When you press button "Change Text" the first word of the phrase above toggles between "Hello" and "Hi" while the rest of the text stays the same.

Avalonia code is very simple - custom code is located only in MainView.xaml and MainView.xaml.cs files.

The button's callback triggers change of the text within the TextBox:

C#
private void ChangeTextButton_Click(object? sender, Avalonia.Interactivity.RoutedEventArgs e)
{
    if (GreetingTextBlock.Text?.StartsWith("Hello") == true)
    {
        GreetingTextBlock.Text = "Hi from Avalonia in-Browser!!!";
    }
    else
    {
        GreetingTextBlock.Text = "Hello from Avalonia in-Browser!!!";
    }
}  

Creating Client C# Avalonia Project

Here I show how to create the Avalonia-in-Browser project using Avalonia Templates.

Note that I assume that we are inside the Solution Folder - AvaInBrowserSample.

To create an Avalonia WebAssembly project I use instructions from Creating Avalonia Web Assembly Project:

  1. I install wasm-tools (or make sure they are installed and up-to-date) by running
    dotnet workload install wasm-tools        
    from a command line.
  2. I update to the latest Avalonia dotnet templates by running command:
    dotnet new install avalonia.templates        
  3. I create folder AvaCode for the Avalonia projects and cd to it using the command line.
  4. From within that folder, I run from the command line:
    dotnet new avalonia.xplat        
  5. This will create the shared project AvaCode (within the same-named folder) and a number of platform specific projects.
  6. I remove most of the platform specific projects leaving only AvaCode.Browser (for building the Avalonia WebAssembly bundle) and AvaCode.Display (for debugging and faster prototyping if needed).

Then I add those three project to my AvaInBrowserSample solution using Visual Studio. I place those projects in a separate solution folder AvaCode:

Image 14

Note that the 3 Avalonia projects are at the top of the image within AvaCode Solution folder.

Now I can build my Avalonia functionality (within AvaCode project) and test it by running it from AvaCode.Desktop project.

I can also test it in the browser by changing the directory to AvaCode.Browser project and executing command "dotnet run" on the command line.

Changes to the Main Project

To make my main ASP.NET project display Avalonia's MainView, I copied the app.cs file from AvaCode.Browser/wwwroot folder into AvaInBrowserSample/wwwroot/css/ folder of the ASP.NET project.

Then I modified the _Layout.cshtml file to have a link to this app.css file, then I added the style that has some magic words: style="margin: 0px; overflow: hidden" to be <body> tag and simplified the area inside the <body> tag to make sure that @RenderBody() call is straight under the tag:

<body style="margin: 0px; overflow: hidden">
    @RenderBody()
    <script src="~/js/site.js" asp-append-version="true"></script>

    @await RenderSectionAsync("Scripts", required: false)
</body>  

Now we need to modify wasmRunner.js file. It will look almost the same as the one from the previous section, but there will be some extra calls between dotnet. and .create() methods:

JavaScript
import { dotnet } from './_framework/dotnet.js'

const is_browser = typeof window != "undefined";
if (!is_browser) throw new Error(`Expected to be running in a browser`);

const dotnetRuntime = await dotnet
    .withDiagnosticTracing(false) // some extra methods
    .withApplicationArgumentsFromQuery() // some extra methods
    .create();

const config = dotnetRuntime.getConfig();

await dotnetRuntime.runMain(config.mainAssemblyName, [window.location.search]);  

The last file to change is Index.cshtml. This file gets uses some CSS classes defined within app.cs file that we copied from AvaCode.Browser project. Without those CSS classes, Avalonia does not take correct space (all space) of the browser:

HTML
<div id="out">
    <div id="out">
        <div id="avalonia-splash">
            <div class="center">
                <h2 class="purple">
                    Please wait while the Avalonia Application Loads
                </h2>
            </div>
        </div>
    </div>
</div>
<script type="module" src="~/wasmRunner.js"></script>  

Conclusion

This article explains embedding C# .NET code into a browser and provides some easy to understand samples. 

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Architect AWebPros
United States United States
I am a software architect and a developer with great passion for new engineering solutions and finding and applying design patterns.

I am passionate about learning new ways of building software and sharing my knowledge with others.

I worked with many various languages including C#, Java and C++.

I fell in love with WPF (and later Silverlight) at first sight. After Microsoft killed Silverlight, I was distraught until I found Avalonia - a great multiplatform package for building UI on Windows, Linux, Mac as well as within browsers (using WASM) and for mobile platforms.

I have my Ph.D. from RPI.

here is my linkedin profile

Comments and Discussions

 
QuestionWASM Loading Time & Blazor's solution Pin
Dewey6hrs 10mins ago
Dewey6hrs 10mins ago 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA17-May-24 7:09
professionalȘtefan-Mihai MOGA17-May-24 7:09 
GeneralRe: My vote of 5 Pin
Nick Polyak17-May-24 7:11
mvaNick Polyak17-May-24 7:11 
GeneralIntriguing article... only read a little bit but I like the bit about Avalonia Pin
Brian C Hart16-May-24 7:02
professionalBrian C Hart16-May-24 7:02 
GeneralRe: Intriguing article... only read a little bit but I like the bit about Avalonia Pin
Nick Polyak16-May-24 7:10
mvaNick Polyak16-May-24 7:10 
GeneralRe: Intriguing article... only read a little bit but I like the bit about Avalonia Pin
Nick Polyak16-May-24 7:16
mvaNick Polyak16-May-24 7:16 
Questioninterested looks like browsable wpf make back Pin
maxoptimus16-May-24 2:04
maxoptimus16-May-24 2:04 
AnswerRe: interested looks like browsable wpf make back Pin
Nick Polyak16-May-24 4:53
mvaNick Polyak16-May-24 4:53 
QuestionYes to Blazor Pin
Paul Gehrman14-May-24 10:04
Paul Gehrman14-May-24 10:04 
AnswerRe: Yes to Blazor Pin
Nick Polyak14-May-24 10:13
mvaNick Polyak14-May-24 10:13 
AnswerRe: Yes to Blazor Pin
maxoptimus16-May-24 2:03
maxoptimus16-May-24 2:03 
QuestionBlazor comment Pin
Anthony Morgan14-May-24 2:28
Anthony Morgan14-May-24 2:28 
AnswerRe: Blazor comment Pin
Nick Polyak14-May-24 5:29
mvaNick Polyak14-May-24 5:29 
GeneralRe: Blazor comment Pin
Member 1623914314-May-24 8:40
Member 1623914314-May-24 8:40 
GeneralRe: Blazor comment Pin
Nick Polyak14-May-24 8:54
mvaNick Polyak14-May-24 8:54 
GeneralRe: Blazor comment Pin
Nick Polyak14-May-24 8:59
mvaNick Polyak14-May-24 8:59 
PraiseAwesome! Pin
GameDevMadeEasy13-May-24 18:58
GameDevMadeEasy13-May-24 18:58 
GeneralRe: Awesome! Pin
Nick Polyak14-May-24 5:32
mvaNick Polyak14-May-24 5:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.