Working with ASP.NET MVC

This commit is contained in:
Frank A. Krueger 2017-11-09 21:00:15 -08:00
parent 5690adac90
commit ebf1d60bde
7 changed files with 167 additions and 27 deletions

View File

@ -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);

View File

@ -6,6 +6,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Ooui\Ooui.csproj" />

View File

@ -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);
}
});
}
}
}

View File

@ -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<string, PendingSession> pendingSessions =
new ConcurrentDictionary<string, PendingSession> ();
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;
}
}
}

View File

@ -27,17 +27,19 @@ namespace Ooui
static readonly byte[] clientJsBytes;
public static byte[] ClientJsBytes => clientJsBytes;
public static string Template { get; set; } = $@"<!DOCTYPE html>
<html>
<head>
<title>@ElementPath</title>
<title>@Title</title>
<meta name=""viewport"" content=""width=device-width, initial-scale=1"" />
<style>@Styles</style>
</head>
<body>
<div id=""ooui-body""></div>
<script src=""/ooui.js""></script>
<script>ooui(""@ElementPath"");</script>
<script>ooui(""@WebSocketPath"");</script>
</body>
</html>";
@ -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;

View File

@ -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()

View File

@ -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?}");
});