Gal Ratner
Gal Ratner is a Techie who lives and works in Los Angeles. Follow galratner on Twitter Google
Creating hybrid HTML5 and WPF applications with self-hosted OWIN Web API SignalR and Awesomium

WPF applications are great desktop technology. WPF is built on top of a resolution-independent and vector-based rendering engine.

WPF applications are data binding, layout, 2-D and 3-D graphics capable, they have animation, styles, templates, documents, media, text, and typography support as well.
WPF uses XAML markup to draw its elements. Some developers prefer the connivance of HTML5 and the browser to XAML, and still like the idea of a desktop powered application.

In this tutorial we will learn how to combine the power of both. We will create a WPF application using XAML, host an embedded HTML5 application inside our app and communicate between both applications seamlessly to give the user a unified experience.


OWIN and self-hosting


OWIN defines a standard interface between .NET web servers and web applications. It essentially allows you to run a small web server in its own process, decoupled from IIS. The process can be hosted inside a .NET application. In our case, our own WPF application


Nuget packages we will need to add OWIN to our app

 

  • Microsoft.AspNet.WebApi.OwinSelfHost – Used to host our API Controllers.
  • Microsoft.AspNet.SignalR.SelfHost – Used to host SignalR.
  • Microsoft.Owin.Cors – Used to enable cross domain requests. In our example, used to enable requests from different ports
  • Microsoft.AspNet.SignalR.Client – Enables WPF to listen and invoke SignalR hub methods.


Awesomium


Awesomium Is a HTML UI Engine, it runs on its own process and acts like an embedded browser, much like the WPF built in WebBrowser control, however, it is HTML5 compliant and is based on the Chrome engine. You can download Awesomium from http://www.awesomium.com/


Let’s start our app by building the UI. This is our XAML. It will create a WPF button and an embedded Awesomium window:

<Window x:Class="WPFSelfHostWebAPI.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:awe="http://schemas.awesomium.com/winfx"
        xmlns:data="http://schemas.awesomium.com/winfx/data"
    xmlns:core="clr-namespace:Awesomium.Core;assembly=Awesomium.Core"
        Title="MainWindow" Height="500" Width="1000">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <Button x:Name="LoadContent" Grid.Column="0" Grid.Row="0" Content="Load Data" FontSize="30" Click="LoadContent_Click" />
        <awe:WebControl x:Name="NavigationWebBrowser" Grid.Column="0" Grid.Row="1"/>
    </Grid>
</Window>



For our API Controller we will simply return an IEnumerable of sales to be shown in our HTML5 app.

using System.Collections.Generic;
using System.Web.Http;
 
namespace WPFSelfHostWebAPI
{
    public class ValuesController : ApiController
    {
        // GET api/values 
        public IEnumerable<Sale> Get()
        {
            return new List<Sale> { new Sale(){ SaleID = "4645", BuyerName = "John Smith"},
               new Sale(){ SaleID = "23455", BuyerName = "Mark Johnson"} };
        }
    }
 
    public class Sale
    {
        public string SaleID { getset; }
        public string BuyerName { getset; }
    }
}

Now that we have out data, let’s start two embedded OWIN servers, one for our Web API calls and one to host our SignalR Hub. SignalR will be used to create real-time communication between both apps. We are going to create a Hub and two clients. The first in our HTML5 app and the second in our WPF app. This will enable bidirectional cross app communication.

This is the code to create both servers:

using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Cors;
using Microsoft.Owin.Hosting;
using Owin;
using System.Web.Http;
using System.Windows;
 
namespace WPFSelfHostWebAPI
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        public static string baseWebAPIAddress = "http://localhost:9000/";
        public static string baseWebSignalRAddress = "http://localhost:8080/";
       
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
 
            WebApp.Start<OWINWebAPIConfig>(url: baseWebAPIAddress);
            WebApp.Start<OWINSignalRConfig>(url: baseWebSignalRAddress);
        }
    }
 
    public class OWINWebAPIConfig 
    { 
        public void Configuration(IAppBuilder appBuilder) 
        {
            appBuilder.UseStaticFiles("/web");
 
            // Configure Web API for self-host. 
            HttpConfiguration config = new HttpConfiguration(); 
            config.Routes.MapHttpRoute( 
                name: "DefaultApi", 
                routeTemplate: "api/{controller}/{id}", 
                defaults: new { id = RouteParameter.Optional } 
            ); 
 
            appBuilder.UseWebApi(config); 
        } 
    }
 
    public class OWINSignalRConfig
    {
        public void Configuration(IAppBuilder appBuilder)
        {
            appBuilder.UseCors(CorsOptions.AllowAll);
            appBuilder.MapSignalR();
        }
    }
 
    public class MainHub : Hub
    {
        public void Send(string name, string message)
        {
            Clients.All.addMessage(name, message);
        }
    }
}

Notice we added our Web API server to port 9000 while our SignalR server is on port 8080. To communicate between both ports we enabled CORS.
All of our static files will be served from the /web directory located inside the application’s bin.
Let’s look at our HTML5 application

<!DOCTYPE html>
 
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title></title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.0.min.js"></script>
    <script src="http://ajax.aspnetcdn.com/ajax/signalr/jquery.signalr-2.0.2.min.js"></script>
    <script src="http://localhost:8080/signalr/hubs"></script>
</head>
<body>
    This is a localy hosted web page. Edit it from the web directory in this application. <br />
    Click the top button to send a hub message to the page.<br />
    When message is received the page will call a Web API controller and display the returned data:<br />
    <input type="button" id="sendmessage" value="I am an HTML Button. Click me to turn the WPF button blue" />
 
        <div id="apidata"></div>
   
    <script type="text/javascript">
        $(function () {
            //Set the hubs URL for the connection
            $.connection.hub.url = "http://localhost:8080/signalr";
 
            // Declare a proxy to reference the hub.
            var chat = $.connection.mainHub;
 
            // Create a function that the hub can call to broadcast messages.
            chat.client.addMessage = function (name, message) {
                // When a WPF load data click is invoked, get the data from the ApiController
                if (name = "button" && message == "getdata")
                    getData();
            };
            // Start the connection.
            $.connection.hub.start().done(function () {
                $('#sendmessage').click(function () {
                    // Call the Send method on the hub.
                    chat.server.send("htmlbutton""blue");
                });
            });
        });
 
        function getData() {
            jQuery.ajax({
                url: "http://localhost:9000/api/Values",
                type: "GET",
                cache: false,
 
                success: function (data) {
                    for (var i = 0; i < data.length; i++) {
                        $("#apidata").append(data[i].SaleID + " " + data[i].BuyerName + "</br>");
                    }
                },
                error: function (msg) { alert(msg); }
            });
        }
        
    </script>
</body>
</html>

We can see that we have a button to send events to the hub and a listener that listens to events. When a getdata event arrives, we use jQuery to request Web API on port 9000 for its sales data and show it to the user. When the HTML button is clicked, we send an event into the Hub.
The last part of our application is the C#  SignalR client. The client creates a proxy to the Hub and listens to incoming messages. If a message comes in from our HTML button, the client runs code to turn the WPF button into a different color. When the WPF button is clicked, the client’s proxy sends a new message to the Hub.

using Microsoft.AspNet.SignalR.Client;
using System;
using System.Windows;
using System.Windows.Media;
 
namespace WPFSelfHostWebAPI
{
    public partial class MainWindow : Window
    {
        HubConnection hubConnection;
        IHubProxy mainHubProxy;
        public MainWindow()
        {
            InitializeComponent();
            NavigationWebBrowser.Source = new Uri("http://localhost:9000/web/default.html");
 
            hubConnection = new HubConnection(App.baseWebSignalRAddress);
            mainHubProxy = hubConnection.CreateHubProxy("MainHub");
            mainHubProxy.On<stringstring>("addMessage", (invoker, data) =>
            {
                if (invoker == "htmlbutton" && data == "blue")
                    Dispatcher.InvokeAsync(() => LoadContent.Background = Brushes.Blue);
            });
            hubConnection.Start();
        }
 
        private void LoadContent_Click(object sender, RoutedEventArgs e)
        {
            mainHubProxy.Invoke("Send"new object[] { "button""getdata" });
        }
    }
}

The result will be a seamless integration between both WPF and HTML parts of our app. A unified user experience that allows us to decide what parts of are app will run on each technology.
When a user clicks a WPF button, the HTML app reacts and when a user clicks an HTML button, the WPF app react.

s.


 

Conclusion


Hybrid apps can make your life easy, they can save you time when designing applications and can help unify User experience between all parts of your enterprise. Consider them for your next desktop app.


Posted 31 Jan 2014 8:03 PM by Gal Ratner
Filed under: , , ,

Powered by Community Server (Non-Commercial Edition), by Telligent Systems