This post was written because I wanted to write a desktop application but don’t particularly like working with WinForms or WPF. As a challenge I decided I should try to use web technologies instead.

The source code for everything covered is available at the bottom of the post.

I chose React as my front end framework because that’s what I’ve been learning. This post is equally applicable to any other front end framework, including Angular and Ember.

Failed First Attempt

My initial attempt started with Electron and Edge.js. Electron would be the application shell and Edge.js would allow me to leverage C# code from my JavaScript. Shortly after starting the project I realized my application was held up by toothpicks. Particularly, it was Edge.js that was the problem – embedding C# in my JavaScript just didn’t feel right.

I am aware embedding C# isn’t the only way to use Edge.js, you could reference an assembly or embed JavaScript in your C# – the point is that mixing the two doesn’t feel right to me.

I decided to do some more reading and learned that I could accomplish the web ui using Chromium Embedded Framework. It sounded like a fun learning experience so I began tinkering.

Chromium Embedded Framework

It didn’t take long for me to realize that my C++ was weak. It had been a few years since I wrote anything meaningful in C++ and Chromium Embedded Framework is mostly C++.

Enter CefSharp

Fortunately someone else had beat me to the punch. I stumbled upon CefSharp, a .NET wrapper around Chromium Embedded Framework. Using CefSharp I was able to embed chromium in my application with minimal code.

I followed some tutorials and some example code from CodeProject and the official CefSharp repository, however, even those examples felt weird. Embedding JavaScript into C# felt like I was using Edge.js all over again. Instead, I decided to self host a web server inside my application.

Self hosting made using React a breeze. Self hosting also gave the added benefit of removing the embedded JavaScript/C# and replacing them with API calls to the web server.

In the following sections we’ll cover the core components of the web server, the WinForms application and the WPF application.

Embedded Web Server

The web server itself is a self hosted OWIN server based on Microsoft’s Owin HttpListener. The server delivers content from an embedded resource folder which contains all the JavaScript, CSS and HTML for the user interface. I used embedded resources to keep the amount of files that live next to the executable to a minimum. Note that I explicitly left out the API portion for this example – I will cover that portion in my next post.

namespace OwinFileHost
{
    public class FileHost
    {
        private static IDisposable Server;
        private static readonly string EmbeddedResourcesNamespace = "OwinFileHost.Resources";
        private static readonly string DefaultFileName = "index.html";
        public static readonly string StartupUrl = "http://localhost:1337/"; 
         
        public static void Start()
        {
            Server = WebApp.Start<FileHost>(StartupUrl);
        }
        
        public static void Stop()
        {
            Server?.Dispose();
        }
        
        public void Configuration(IAppBuilder app)
        {
            var options = new FileServerOptions
            {
                FileSystem = new EmbeddedResourceFileSystem(EmbeddedResourcesNamespace)
            };
            options.DefaultFilesOptions.DefaultFileNames = new[] { DefaultFileName };
            app.UseFileServer(options);
        }
    }
}

If the above code looks foreign to you, I encourage you to check out the official page for OWIN and Project Katana, Microsoft’s implementation of OWIN. There is an excellent overview of Project Katana available on the ASP.NET website.

WinForms

In the WinForms application we start by initializing Cef, followed by a call to start the file host and then a call to run the main form. The BrowserForm class houses the embedded chromium browser.

public static class Program
{
    [STAThread]
    static void Main()
    {
        if(Cef.Initialize())
        {
            FileHost.Start();
            Application.Run(new BrowserForm());
            FileHost.Stop();
            Cef.Shutdown();
        }
    }
}

Inside the BrowserForm class we attach an event handler for the Load event. In the Load event we create an instance of the ChromiumWebBrowser and add it as a control to a panel.

public partial class BrowserForm : Form
{
    public IWinFormsWebBrowser Browser { get; private set; }
 
    public BrowserForm()
    {
        InitializeComponent();
        Load += OnLoad;
    }
 
    private void OnLoad(object sender, EventArgs e)
    {
        var browser = new ChromiumWebBrowser(FileHost.StartupUrl)
        {
            Dock = DockStyle.Fill
        };
        BrowserPanel.Controls.Add(browser);
        Browser = browser;
    }
}

The above code is enough to start using web technologies for the user interface. In the next section I’ll show you how I accomplished the same thing with WPF.

WPF

There’s a few differences between the WinForms and the WPF process. Inside the App.xaml.cs we override the OnStartup method and add an exit event handler.

The OnStartup creates an instance of CefSettings and makes a call to SetOffScreenRenderingBestPerformanceArgs. The function call will disable WebGL and automatically determine the best command line arguments for Cef. If you try running the application without settings command line arguments you’ll get a sluggish ui experience.

The App_OnExit event handler shuts down Cef and stops the file host.

public partial class App : Application
{
    protected override void OnStartup(StartupEventArgs e)
    {
        var cefSettings = new CefSettings();
        cefSettings.SetOffScreenRenderingBestPerformanceArgs();
        if (Cef.Initialize(cefSettings))
        {
            FileHost.Start();
            base.OnStartup(e);
        }
        else
        {
            Current.Shutdown();
        }
 
    }
 
    private void App_OnExit(object sender, ExitEventArgs e)
    {
        if (Cef.IsInitialized)
        {
            Cef.Shutdown();
        }
        FileHost.Stop();
    }
}

MainWindow.xaml consists of two xaml elements, a grid and the ChromiumWebBrowser. Note that I named the Window BrowserWindow and set the data context in XAML to the element with the name BrowserWindow. The ChromiumWebBrowser’s address is bound to a property in the code behind.

<Window x:Class="WpfChromiumExample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:cef="clr-namespace:CefSharp.Wpf;assembly=CefSharp.Wpf"
        mc:Ignorable="d"
        Title="MainWindow" 
        x:Name="BrowserWindow" 
        DataContext="{Binding ElementName=BrowserWindow}"
        WindowState="Maximized">
    <Grid>
        <cef:ChromiumWebBrowser Address="{Binding Path=StartAddress}" />
    </Grid>
</Window>

The code behind file contains a single string property, StartAddress.

public partial class MainWindow : Window
{
    public string StartAddress {  get { return FileHost.StartupUrl;  } }

    public MainWindow()
    {
        InitializeComponent();
    }
}

Conclusion

That’s essentially all there is. If you copy this code, install the appropriate NuGet packages and add your own embedded resources you can start building out your own applications with web technology.

I will be using this method in my own applications for fun. I’m not recommending you do the same, I just wanted to share what I learned and what I couldn’t find elsewhere.

The source code for the code in this project is located in my EmbeddedChromiumExample repository on GitHub. You must set the project configuration to be either x86 or x64 as CefSharp will not work without a targeted CPU.

The following links are related to this post and might interest you.

If you have any suggestions, questions, or comments, feel free to post them below.


Leave a comment below.