diff --git a/Ooui.AspNetCore/ElementResult.cs b/Ooui.AspNetCore/ElementResult.cs
index 19faf23..f43d342 100644
--- a/Ooui.AspNetCore/ElementResult.cs
+++ b/Ooui.AspNetCore/ElementResult.cs
@@ -6,20 +6,21 @@ using Microsoft.AspNetCore.Mvc;
namespace Ooui.AspNetCore
{
public class ElementResult : ActionResult
- {
+ {
+ readonly Element element;
+
public ElementResult (Element element)
{
-
+ this.element = element;
}
public override async Task ExecuteResultAsync (ActionContext context)
{
- var path = context.HttpContext.Request.Path;
var response = context.HttpContext.Response;
-
response.StatusCode = 200;
- response.ContentType = "text/html";
- var html = Encoding.UTF8.GetBytes (UI.RenderTemplate (path));
+ response.ContentType = "text/html; charset=utf-8";
+ var sessionId = WebSocketHandler.BeginSession (context.HttpContext, element);
+ var html = Encoding.UTF8.GetBytes (UI.RenderTemplate (WebSocketHandler.WebSocketPath + "?id=" + sessionId));
response.ContentLength = html.Length;
using (var s = response.Body) {
await s.WriteAsync (html, 0, html.Length).ConfigureAwait (false);
diff --git a/Ooui.AspNetCore/Ooui.AspNetCore.csproj b/Ooui.AspNetCore/Ooui.AspNetCore.csproj
index 372fb3c..bb62fe3 100644
--- a/Ooui.AspNetCore/Ooui.AspNetCore.csproj
+++ b/Ooui.AspNetCore/Ooui.AspNetCore.csproj
@@ -6,6 +6,7 @@
+
diff --git a/Ooui.AspNetCore/OouiMiddlewareExtensions.cs b/Ooui.AspNetCore/OouiMiddlewareExtensions.cs
new file mode 100644
index 0000000..deaa51e
--- /dev/null
+++ b/Ooui.AspNetCore/OouiMiddlewareExtensions.cs
@@ -0,0 +1,55 @@
+using System;
+using Ooui.AspNetCore;
+
+namespace Microsoft.AspNetCore.Builder
+{
+ public static class OouiMiddlewareExtensions
+ {
+ public static void UseOoui (this IApplicationBuilder app, string jsPath = "/ooui.js", string webSocketPath = "/ooui.ws", TimeSpan? sessionTimeout = null)
+ {
+ if (string.IsNullOrWhiteSpace (webSocketPath))
+ throw new ArgumentException ("A path to be used for Ooui web sockets must be specified", nameof (webSocketPath));
+
+ if (string.IsNullOrWhiteSpace (jsPath))
+ throw new ArgumentException ("A path to be used for Ooui JavaScript must be specified", nameof (jsPath));
+
+ WebSocketHandler.WebSocketPath = webSocketPath;
+
+ if (sessionTimeout.HasValue) {
+ WebSocketHandler.SessionTimeout = sessionTimeout.Value;
+ }
+
+ var webSocketOptions = new WebSocketOptions () {
+ KeepAliveInterval = WebSocketHandler.SessionTimeout,
+ ReceiveBufferSize = 4 * 1024
+ };
+ app.UseWebSockets (webSocketOptions);
+
+ app.Use (async (context, next) =>
+ {
+ if (context.Request.Path == jsPath) {
+ var response = context.Response;
+ var clientJsBytes = Ooui.UI.ClientJsBytes;
+ response.StatusCode = 200;
+ response.ContentLength = clientJsBytes.Length;
+ response.ContentType = "application/javascript; charset=utf-8";
+ response.Headers.Add ("Cache-Control", "public, max-age=3600");
+ using (var s = response.Body) {
+ await s.WriteAsync (clientJsBytes, 0, clientJsBytes.Length).ConfigureAwait (false);
+ }
+ }
+ else if (context.Request.Path == WebSocketHandler.WebSocketPath) {
+ if (context.WebSockets.IsWebSocketRequest) {
+ await WebSocketHandler.HandleWebSocketRequestAsync (context).ConfigureAwait (false);
+ }
+ else {
+ context.Response.StatusCode = 400;
+ }
+ }
+ else {
+ await next ().ConfigureAwait (false);
+ }
+ });
+ }
+ }
+}
diff --git a/Ooui.AspNetCore/WebSocketHandler.cs b/Ooui.AspNetCore/WebSocketHandler.cs
new file mode 100644
index 0000000..0c22f74
--- /dev/null
+++ b/Ooui.AspNetCore/WebSocketHandler.cs
@@ -0,0 +1,82 @@
+using System;
+using System.Collections.Concurrent;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+
+namespace Ooui.AspNetCore
+{
+ public static class WebSocketHandler
+ {
+ public static string WebSocketPath { get; set; } = "/ooui.ws";
+
+ public static TimeSpan SessionTimeout { get; set; } = TimeSpan.FromMinutes (5);
+
+ static readonly ConcurrentDictionary pendingSessions =
+ new ConcurrentDictionary ();
+
+ public static string BeginSession (HttpContext context, Element element)
+ {
+ var id = Guid.NewGuid ().ToString ("N");
+
+ var s = new PendingSession {
+ Element = element,
+ RequestTimeUtc = DateTime.UtcNow,
+ };
+
+ if (!pendingSessions.TryAdd (id, s)) {
+ throw new Exception ("Failed to schedule pending session");
+ }
+
+ return id;
+ }
+
+ public static async Task HandleWebSocketRequestAsync (HttpContext context)
+ {
+ //
+ // Make sure we get a good ID
+ //
+ if (!context.Request.Query.TryGetValue ("id", out var idValues)) {
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
+ return;
+ }
+
+ var id = idValues.FirstOrDefault ();
+ if (id == null || id.Length != 32) {
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
+ return;
+ }
+
+ //
+ // Find the pending session
+ //
+ if (!pendingSessions.TryRemove (id, out var pendingSession)) {
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
+ return;
+ }
+
+ //
+ // Reject the session if it's old
+ //
+ if ((DateTime.UtcNow - pendingSession.RequestTimeUtc) > SessionTimeout) {
+ context.Response.StatusCode = StatusCodes.Status400BadRequest;
+ return;
+ }
+
+ //
+ // OK, Run
+ //
+ var token = CancellationToken.None;
+ var webSocket = await context.WebSockets.AcceptWebSocketAsync ("ooui");
+ var session = new Ooui.UI.Session (webSocket, pendingSession.Element, token);
+ await session.RunAsync ().ConfigureAwait (false);
+ }
+
+ class PendingSession
+ {
+ public Element Element;
+ public DateTime RequestTimeUtc;
+ }
+ }
+}
diff --git a/Ooui/UI.cs b/Ooui/UI.cs
index 753639a..1e4bd24 100644
--- a/Ooui/UI.cs
+++ b/Ooui/UI.cs
@@ -27,17 +27,19 @@ namespace Ooui
static readonly byte[] clientJsBytes;
+ public static byte[] ClientJsBytes => clientJsBytes;
+
public static string Template { get; set; } = $@"
- @ElementPath
+ @Title
-
+
";
@@ -297,9 +299,9 @@ namespace Ooui
}
}
- public static string RenderTemplate (string elementPath)
+ public static string RenderTemplate (string webSocketPath)
{
- return Template.Replace ("@ElementPath", elementPath).Replace ("@Styles", rules.ToString ());
+ return Template.Replace ("@WebSocketPath", webSocketPath).Replace ("@Styles", rules.ToString ());
}
class DataHandler : RequestHandler
@@ -454,7 +456,7 @@ namespace Ooui
Console.ResetColor ();
}
- class Session
+ public class Session
{
readonly WebSocket webSocket;
readonly Element element;
diff --git a/PlatformSamples/AspNetCoreMvc/Controllers/HomeController.cs b/PlatformSamples/AspNetCoreMvc/Controllers/HomeController.cs
index 10f2378..745e96f 100644
--- a/PlatformSamples/AspNetCoreMvc/Controllers/HomeController.cs
+++ b/PlatformSamples/AspNetCoreMvc/Controllers/HomeController.cs
@@ -15,8 +15,8 @@ namespace AspNetCoreMvc.Controllers
{
public IActionResult Index()
{
- var button = new Button ();
- return new ElementResult (button);
+ var element = new Label { Text = "Hello Oooooui from Controller" };
+ return new ElementResult (element);
}
public IActionResult About()
diff --git a/PlatformSamples/AspNetCoreMvc/Startup.cs b/PlatformSamples/AspNetCoreMvc/Startup.cs
index e4ec3f9..cc5ff26 100644
--- a/PlatformSamples/AspNetCoreMvc/Startup.cs
+++ b/PlatformSamples/AspNetCoreMvc/Startup.cs
@@ -11,7 +11,7 @@ namespace AspNetCoreMvc
{
public class Startup
{
- public Startup(IConfiguration configuration)
+ public Startup (IConfiguration configuration)
{
Configuration = configuration;
}
@@ -19,29 +19,28 @@ namespace AspNetCoreMvc
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
- public void ConfigureServices(IServiceCollection services)
+ public void ConfigureServices (IServiceCollection services)
{
- services.AddMvc();
+ services.AddMvc ();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
- public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ public void Configure (IApplicationBuilder app, IHostingEnvironment env)
{
- if (env.IsDevelopment())
- {
- app.UseDeveloperExceptionPage();
+ if (env.IsDevelopment ()) {
+ app.UseDeveloperExceptionPage ();
}
- else
- {
- app.UseExceptionHandler("/Home/Error");
+ else {
+ app.UseExceptionHandler ("/Home/Error");
}
- app.UseStaticFiles();
+ app.UseStaticFiles ();
- app.UseMvc(routes =>
- {
- routes.MapRoute(
+ app.UseOoui ();
+
+ app.UseMvc (routes => {
+ routes.MapRoute (
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});